@devvistatech/devvista-kit 0.0.9 → 0.0.10
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/CHANGELOG.md +12 -12
- package/LICENSE +6 -6
- package/README.md +15 -15
- package/app/about/page.tsx +298 -298
- package/app/adRequest/page.tsx +549 -549
- package/app/analytics/page.tsx +346 -346
- package/app/api/about/route.ts +306 -306
- package/app/api/adRequest/route.ts +567 -567
- package/app/api/analytics/[reportType]/route.ts +337 -337
- package/app/api/bio/route.ts +313 -313
- package/app/api/blog/route.ts +306 -306
- package/app/api/chat/route.ts +14 -14
- package/app/api/contact/route.ts +409 -409
- package/app/api/contacts/route.ts +224 -224
- package/app/api/files/route.ts +429 -429
- package/app/api/gallery-data/route.ts +735 -735
- package/app/api/schedule/route.ts +455 -455
- package/app/api/sync-user/route.ts +131 -131
- package/app/api/trial-request/route.ts +297 -297
- package/app/blog/[id]/page.tsx +288 -288
- package/app/blog/page.tsx +216 -216
- package/app/contact/page.tsx +284 -284
- package/app/faq/page.tsx +191 -191
- package/app/gallery/page.tsx +315 -315
- package/app/globals.css +58 -58
- package/app/layout.tsx +110 -110
- package/app/not-found.tsx +20 -20
- package/app/page.tsx +338 -338
- package/app/schedule/page.tsx +660 -660
- package/bin/init.js +219 -219
- package/components/addOns/functional/BioEditor.tsx +446 -446
- package/components/addOns/functional/CalendlyWidget.tsx +107 -107
- package/components/addOns/functional/ClassList.tsx +145 -145
- package/components/addOns/functional/ClassPopup.tsx +398 -398
- package/components/addOns/functional/ContactForm.tsx +284 -284
- package/components/addOns/functional/FileUploader.tsx +294 -294
- package/components/addOns/functional/ImageDescCarousel.tsx +730 -730
- package/components/addOns/functional/NewUserAnalytics.tsx +100 -100
- package/components/addOns/functional/ScheduleCarousel.tsx +171 -171
- package/components/addOns/functional/aboutSections/AboutSection.tsx +544 -544
- package/components/addOns/functional/aboutSections/constants/aboutSection.ts +65 -65
- package/components/addOns/functional/blogSections/BlogDashboard.tsx +184 -184
- package/components/addOns/functional/blogSections/BlogFormPopUp.tsx +554 -554
- package/components/addOns/functional/blogSections/BlogList.tsx +148 -148
- package/components/addOns/functional/blogSections/BlogSidebar.tsx +58 -58
- package/components/addOns/functional/blogSections/constants/blogDashboard.ts +28 -28
- package/components/addOns/functional/blogSections/constants/blogFormPopUp.ts +97 -97
- package/components/addOns/functional/blogSections/constants/blogList.ts +22 -22
- package/components/addOns/functional/blogSections/constants/blogSidebar.ts +15 -15
- package/components/addOns/functional/contactsDashboard/ContactsDashboard.tsx +366 -366
- package/components/addOns/functional/contactsDashboard/constants/contactsDashboard.ts +70 -70
- package/components/addOns/functional/galleries/GalleryComplex.tsx +836 -836
- package/components/addOns/functional/galleries/GallerySimple.tsx +509 -509
- package/components/addOns/functional/galleries/constants/galleryComplex.ts +106 -106
- package/components/addOns/functional/galleries/constants/gallerySimple.ts +76 -76
- package/components/addOns/functional/schedules/ScheduleGridOne.tsx +262 -262
- package/components/addOns/functional/schedules/ScheduleGridTwo.tsx +294 -294
- package/components/addOns/functional/schedules/ScheduleGridTwoBasic.tsx +288 -288
- package/components/addOns/functional/schedules/SchedulerForm.tsx +428 -428
- package/components/addOns/functional/schedules/constants/ScheduleGridTwo.ts +40 -40
- package/components/addOns/functional/schedules/constants/ScheduleGridTwoBasic.ts +40 -40
- package/components/addOns/functional/schedules/constants/SchedulerForm.ts +65 -65
- package/components/addOns/functional/schedules/constants/scheduleGridOne.ts +54 -54
- package/components/addOns/non-functional/AnnouncementBanner.tsx +46 -46
- package/components/addOns/non-functional/FeaturesSection.tsx +62 -62
- package/components/addOns/non-functional/Heros/HeroSection.tsx +142 -142
- package/components/addOns/non-functional/IconBubble.tsx +49 -49
- package/components/addOns/non-functional/SampleCarousel.tsx +204 -204
- package/components/addOns/non-functional/Testimonials.tsx +334 -334
- package/components/addOns/non-functional/ThreeSetGallery.tsx +63 -63
- package/components/addOns/non-functional/aboutSections/AboutSection.tsx +62 -62
- package/components/addOns/non-functional/aboutSections/constants/aboutSection.ts +24 -24
- package/components/addOns/non-functional/imageCarousels/ProductSlider.tsx +117 -117
- package/components/addOns/non-functional/imageCarousels/ProgramCarousel.tsx +232 -232
- package/components/addOns/non-functional/imageCarousels/constants/programCarousel.ts +39 -39
- package/components/addOns/non-functional/imageCarousels/constants/programSlider.ts +36 -36
- package/components/addOns/non-functional/spinner.tsx +21 -21
- package/components/footers/footer.tsx +453 -453
- package/components/navBars/navbar.tsx +310 -310
- package/components/other/accordion.tsx +58 -58
- package/components/other/admin-menu.tsx +68 -68
- package/components/other/alert-dialog.tsx +141 -141
- package/components/other/alert.tsx +59 -59
- package/components/other/aspect-ratio.tsx +7 -7
- package/components/other/avatar.tsx +50 -50
- package/components/other/badge.tsx +36 -36
- package/components/other/breadcrumb.tsx +115 -115
- package/components/other/button.tsx +738 -738
- package/components/other/calendar.tsx +66 -66
- package/components/other/card.tsx +86 -86
- package/components/other/carousel.tsx +274 -274
- package/components/other/chart.tsx +363 -363
- package/components/other/checkbox.tsx +30 -30
- package/components/other/collapsible.tsx +11 -11
- package/components/other/command.tsx +155 -155
- package/components/other/context-menu.tsx +200 -200
- package/components/other/dialog.tsx +122 -122
- package/components/other/drawer.tsx +118 -118
- package/components/other/dropdown-menu.tsx +200 -200
- package/components/other/form.tsx +179 -179
- package/components/other/hover-card.tsx +29 -29
- package/components/other/input-otp.tsx +71 -71
- package/components/other/input.tsx +25 -25
- package/components/other/label.tsx +26 -26
- package/components/other/menubar.tsx +236 -236
- package/components/other/mobile-icon.tsx +21 -21
- package/components/other/navigation-menu.tsx +128 -128
- package/components/other/pagination.tsx +117 -117
- package/components/other/popover.tsx +31 -31
- package/components/other/progress.tsx +28 -28
- package/components/other/radio-group.tsx +44 -44
- package/components/other/resizable.tsx +45 -45
- package/components/other/scroll-area.tsx +48 -48
- package/components/other/select.tsx +160 -160
- package/components/other/separator.tsx +31 -31
- package/components/other/sheet.tsx +140 -140
- package/components/other/skeleton.tsx +15 -15
- package/components/other/slider.tsx +28 -28
- package/components/other/social-icons.tsx +39 -39
- package/components/other/sonner.tsx +31 -31
- package/components/other/switch.tsx +29 -29
- package/components/other/table.tsx +117 -117
- package/components/other/tabs.tsx +55 -55
- package/components/other/textarea.tsx +24 -24
- package/components/other/toast.tsx +122 -122
- package/components/other/toaster.tsx +35 -35
- package/components/other/toggle-group.tsx +61 -61
- package/components/other/toggle.tsx +45 -45
- package/components/other/tooltip.tsx +30 -30
- package/components/theme-provider.tsx +8 -8
- package/components/types.ts +49 -49
- package/dist/.next/types/app/api/about/route.js +52 -0
- package/dist/.next/types/app/api/blog/route.js +52 -0
- package/dist/.next/types/app/api/files/route.js +52 -0
- package/dist/.next/types/app/api/schedule/route.js +52 -0
- package/dist/.next/types/app/api/sync-user/route.js +52 -0
- package/dist/.next/types/app/layout.js +22 -0
- package/dist/.next/types/app/page.js +22 -0
- package/dist/app/about/page.jsx +258 -0
- package/dist/app/adRequest/page.jsx +531 -0
- package/dist/app/analytics/page.jsx +298 -0
- package/dist/app/api/about/route.js +285 -0
- package/dist/app/api/adRequest/route.js +440 -0
- package/dist/app/api/analytics/[reportType]/route.js +357 -0
- package/dist/app/api/bio/route.js +293 -0
- package/dist/app/api/blog/route.js +366 -0
- package/dist/app/api/chat/route.js +58 -0
- package/dist/app/api/contact/route.js +163 -0
- package/dist/app/api/contacts/route.js +234 -0
- package/dist/app/api/files/route.js +444 -0
- package/dist/app/api/gallery-data/route.js +719 -0
- package/dist/app/api/schedule/route.js +461 -0
- package/dist/app/api/sync-user/route.js +186 -0
- package/dist/app/api/trial-request/route.js +165 -0
- package/dist/app/blog/[id]/page.jsx +312 -0
- package/dist/app/blog/page.jsx +210 -0
- package/dist/app/constants/about.js +32 -0
- package/dist/app/constants/adRequest.js +113 -0
- package/dist/app/constants/contact.js +40 -0
- package/dist/app/constants/faq.js +36 -0
- package/dist/app/constants/gallery.js +42 -0
- package/dist/app/constants/page.js +69 -0
- package/dist/app/constants/schedule.js +71 -0
- package/dist/app/contact/page.jsx +119 -0
- package/dist/app/faq/page.jsx +97 -0
- package/dist/app/gallery/page.jsx +281 -0
- package/dist/app/layout.jsx +45 -0
- package/dist/app/not-found.jsx +14 -0
- package/dist/app/page.jsx +324 -0
- package/dist/app/schedule/page.jsx +500 -0
- package/dist/components/addOns/functional/BioEditor.jsx +187 -0
- package/dist/components/addOns/functional/CalendlyWidget.jsx +61 -0
- package/dist/components/addOns/functional/ClassList.jsx +158 -0
- package/dist/components/addOns/functional/ClassPopup.jsx +300 -0
- package/dist/components/addOns/functional/ContactForm.jsx +219 -0
- package/dist/components/addOns/functional/FileUploader.jsx +222 -0
- package/dist/components/addOns/functional/ImageDescCarousel.jsx +491 -0
- package/dist/components/addOns/functional/NewUserAnalytics.jsx +71 -0
- package/dist/components/addOns/functional/ScheduleCarousel.jsx +68 -0
- package/dist/components/addOns/functional/aboutSections/AboutSection.jsx +372 -0
- package/dist/components/addOns/functional/aboutSections/constants/aboutSection.js +65 -0
- package/dist/components/addOns/functional/blogSections/BlogDashboard.jsx +111 -0
- package/dist/components/addOns/functional/blogSections/BlogFormPopUp.jsx +465 -0
- package/dist/components/addOns/functional/blogSections/BlogList.jsx +170 -0
- package/dist/components/addOns/functional/blogSections/BlogSidebar.jsx +35 -0
- package/dist/components/addOns/functional/blogSections/constants/blogDashboard.js +28 -0
- package/dist/components/addOns/functional/blogSections/constants/blogFormPopUp.js +97 -0
- package/dist/components/addOns/functional/blogSections/constants/blogList.js +22 -0
- package/dist/components/addOns/functional/blogSections/constants/blogSidebar.js +15 -0
- package/dist/components/addOns/functional/contactsDashboard/ContactsDashboard.jsx +355 -0
- package/dist/components/addOns/functional/contactsDashboard/constants/contactsDashboard.js +70 -0
- package/dist/components/addOns/functional/galleries/GalleryComplex.jsx +605 -0
- package/dist/components/addOns/functional/galleries/GallerySimple.jsx +363 -0
- package/dist/components/addOns/functional/galleries/constants/galleryComplex.js +106 -0
- package/dist/components/addOns/functional/galleries/constants/gallerySimple.js +76 -0
- package/dist/components/addOns/functional/schedules/ScheduleGridOne.jsx +167 -0
- package/dist/components/addOns/functional/schedules/ScheduleGridTwo.jsx +100 -0
- package/dist/components/addOns/functional/schedules/ScheduleGridTwoBasic.jsx +97 -0
- package/dist/components/addOns/functional/schedules/SchedulerForm.jsx +188 -0
- package/dist/components/addOns/functional/schedules/constants/ScheduleGridTwo.js +40 -0
- package/dist/components/addOns/functional/schedules/constants/ScheduleGridTwoBasic.js +40 -0
- package/dist/components/addOns/functional/schedules/constants/SchedulerForm.js +65 -0
- package/dist/components/addOns/functional/schedules/constants/scheduleGridOne.js +54 -0
- package/dist/components/addOns/non-functional/AnnouncementBanner.jsx +24 -0
- package/dist/components/addOns/non-functional/FeaturesSection.jsx +38 -0
- package/dist/components/addOns/non-functional/HeroSection.jsx +71 -0
- package/dist/components/addOns/non-functional/Heros/HeroSection.jsx +71 -0
- package/dist/components/addOns/non-functional/IconBubble.jsx +36 -0
- package/dist/components/addOns/non-functional/SampleCarousel.jsx +114 -0
- package/dist/components/addOns/non-functional/Testimonials.jsx +177 -0
- package/dist/components/addOns/non-functional/ThreeSetGallery.jsx +40 -0
- package/dist/components/addOns/non-functional/aboutSections/AboutSection.jsx +35 -0
- package/dist/components/addOns/non-functional/aboutSections/constants/aboutSection.js +24 -0
- package/dist/components/addOns/non-functional/imageCarousels/ProductSlider.jsx +80 -0
- package/dist/components/addOns/non-functional/imageCarousels/ProgramCarousel.jsx +155 -0
- package/dist/components/addOns/non-functional/imageCarousels/constants/programCarousel.js +39 -0
- package/dist/components/addOns/non-functional/imageCarousels/constants/programSlider.js +36 -0
- package/dist/components/addOns/non-functional/spinner.jsx +13 -0
- package/dist/components/footers/footer.jsx +217 -0
- package/dist/components/navBars/navbar.jsx +159 -0
- package/dist/components/other/accordion.jsx +40 -0
- package/dist/components/other/admin-menu.jsx +34 -0
- package/dist/components/other/alert-dialog.jsx +64 -0
- package/dist/components/other/alert.jsx +41 -0
- package/dist/components/other/aspect-ratio.jsx +4 -0
- package/dist/components/other/avatar.jsx +31 -0
- package/dist/components/other/badge.jsx +32 -0
- package/dist/components/other/breadcrumb.jsx +57 -0
- package/dist/components/other/button.jsx +322 -0
- package/dist/components/other/calendar.jsx +43 -0
- package/dist/components/other/card.jsx +44 -0
- package/dist/components/other/carousel.jsx +140 -0
- package/dist/components/other/chart.jsx +182 -0
- package/dist/components/other/checkbox.jsx +26 -0
- package/dist/components/other/collapsible.jsx +6 -0
- package/dist/components/other/command.jsx +68 -0
- package/dist/components/other/context-menu.jsx +88 -0
- package/dist/components/other/dialog.jsx +60 -0
- package/dist/components/other/drawer.jsx +60 -0
- package/dist/components/other/dropdown-menu.jsx +90 -0
- package/dist/components/other/form.jsx +89 -0
- package/dist/components/other/hover-card.jsx +23 -0
- package/dist/components/other/input-otp.jsx +46 -0
- package/dist/components/other/input.jsx +19 -0
- package/dist/components/other/label.jsx +23 -0
- package/dist/components/other/login-popup.jsx +1 -0
- package/dist/components/other/menubar.jsx +96 -0
- package/dist/components/other/mobile-icon.jsx +11 -0
- package/dist/components/other/navigation-menu.jsx +62 -0
- package/dist/components/other/pagination.jsx +63 -0
- package/dist/components/other/popover.jsx +25 -0
- package/dist/components/other/progress.jsx +23 -0
- package/dist/components/other/radio-group.jsx +31 -0
- package/dist/components/other/resizable.jsx +29 -0
- package/dist/components/other/scroll-area.jsx +36 -0
- package/dist/components/other/select.jsx +83 -0
- package/dist/components/other/separator.jsx +21 -0
- package/dist/components/other/sheet.jsx +74 -0
- package/dist/components/other/signup-popup.jsx +1 -0
- package/dist/components/other/skeleton.jsx +17 -0
- package/dist/components/other/slider.jsx +26 -0
- package/dist/components/other/social-icons.jsx +15 -0
- package/dist/components/other/sonner.jsx +27 -0
- package/dist/components/other/switch.jsx +23 -0
- package/dist/components/other/table.jsx +56 -0
- package/dist/components/other/tabs.jsx +32 -0
- package/dist/components/other/textarea.jsx +19 -0
- package/dist/components/other/toast.jsx +58 -0
- package/dist/components/other/toaster.jsx +31 -0
- package/dist/components/other/toggle-group.jsx +41 -0
- package/dist/components/other/toggle.jsx +39 -0
- package/dist/components/other/tooltip.jsx +24 -0
- package/dist/components/theme-provider.jsx +18 -0
- package/dist/components/types.js +1 -0
- package/dist/hooks/use-toast.js +135 -0
- package/dist/lib/auth-context.jsx +144 -0
- package/dist/lib/constants/about.js +32 -0
- package/dist/lib/constants/adRequest.js +113 -0
- package/dist/lib/constants/contact.js +40 -0
- package/dist/lib/constants/faq.js +36 -0
- package/dist/lib/constants/gallery.js +42 -0
- package/dist/lib/constants/page.js +69 -0
- package/dist/lib/constants/schedule.js +71 -0
- package/dist/lib/google-analytics.jsx +148 -0
- package/dist/lib/utils.js +9 -0
- package/dist/lib/verify-user.js +142 -0
- package/dist/middleware.js +37 -0
- package/dist/tailwind.config.js +86 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/hooks/use-toast.ts +188 -188
- package/lib/auth-context.tsx +130 -130
- package/lib/constants/about.ts +34 -34
- package/lib/constants/adRequest.ts +113 -113
- package/lib/constants/contact.ts +40 -40
- package/lib/constants/faq.ts +34 -21
- package/lib/constants/gallery.ts +42 -42
- package/lib/constants/page.ts +69 -69
- package/lib/constants/schedule.ts +71 -71
- package/lib/google-analytics.tsx +97 -97
- package/lib/verify-user.ts +117 -117
- package/middleware.ts +42 -42
- package/netlify.toml +5 -5
- package/next.config.js +10 -10
- package/package.json +115 -115
- package/tailwind.config.ts +89 -89
- package/tsconfig.json +23 -23
|
@@ -1,335 +1,335 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import { motion } from "framer-motion";
|
|
4
|
-
import { useRef, useState, useEffect } from "react";
|
|
5
|
-
|
|
6
|
-
interface TestimonialModalProps {
|
|
7
|
-
isOpen: boolean;
|
|
8
|
-
onClose: () => void;
|
|
9
|
-
quote: string;
|
|
10
|
-
author: string;
|
|
11
|
-
isMobile: boolean;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
interface TestimonialsProps {
|
|
15
|
-
isMobile: boolean;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
const TestimonialModal: React.FC<TestimonialModalProps> = ({ isOpen, onClose, quote, author, isMobile }) => {
|
|
19
|
-
const modalRef = useRef<HTMLDivElement>(null);
|
|
20
|
-
|
|
21
|
-
const handleClickOutside = (event: React.MouseEvent<HTMLDivElement>) => {
|
|
22
|
-
if (modalRef.current && !modalRef.current.contains(event.target as Node)) {
|
|
23
|
-
onClose();
|
|
24
|
-
}
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
useEffect(() => {
|
|
28
|
-
if (isOpen) {
|
|
29
|
-
const firstFocusable = modalRef.current?.querySelector("button") as HTMLElement;
|
|
30
|
-
firstFocusable?.focus();
|
|
31
|
-
document.body.style.overflow = "hidden";
|
|
32
|
-
} else {
|
|
33
|
-
document.body.style.overflow = "auto";
|
|
34
|
-
}
|
|
35
|
-
return () => {
|
|
36
|
-
document.body.style.overflow = "auto";
|
|
37
|
-
};
|
|
38
|
-
}, [isOpen]);
|
|
39
|
-
|
|
40
|
-
if (!isOpen) return null;
|
|
41
|
-
|
|
42
|
-
return (
|
|
43
|
-
<div
|
|
44
|
-
className="fixed inset-0 bg-black/50 flex items-center justify-center z-[10000] p-4"
|
|
45
|
-
onClick={handleClickOutside}
|
|
46
|
-
aria-modal="true"
|
|
47
|
-
role="dialog"
|
|
48
|
-
>
|
|
49
|
-
<motion.div
|
|
50
|
-
ref={modalRef}
|
|
51
|
-
initial={{ opacity: 0, scale: 0.95 }}
|
|
52
|
-
animate={{ opacity: 1, scale: 1 }}
|
|
53
|
-
exit={{ opacity: 0, scale: 0.95 }}
|
|
54
|
-
className="bg-white rounded-2xl p-6 sm:p-8 max-w-lg w-full overflow-y-auto relative glassmorphism"
|
|
55
|
-
style={{ maxHeight: isMobile ? "50vh" : "80vh" }}
|
|
56
|
-
>
|
|
57
|
-
<button
|
|
58
|
-
onClick={onClose}
|
|
59
|
-
className="absolute top-4 right-4 text-gray-500 hover:text-gray-700"
|
|
60
|
-
aria-label="Close testimonial modal"
|
|
61
|
-
>
|
|
62
|
-
<svg
|
|
63
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
64
|
-
width="24"
|
|
65
|
-
height="24"
|
|
66
|
-
viewBox="0 0 24 24"
|
|
67
|
-
fill="none"
|
|
68
|
-
stroke="currentColor"
|
|
69
|
-
strokeWidth="2"
|
|
70
|
-
strokeLinecap="round"
|
|
71
|
-
strokeLinejoin="round"
|
|
72
|
-
>
|
|
73
|
-
<path d="M18 6L6 18M6 6l12 12" />
|
|
74
|
-
</svg>
|
|
75
|
-
</button>
|
|
76
|
-
<p className="text-[#2D2D2D] text-lg sm:text-xl font-medium italic mb-4">
|
|
77
|
-
“{quote}”
|
|
78
|
-
</p>
|
|
79
|
-
<p className="text-[#2D2D2D] text-base sm:text-lg font-medium text-right">
|
|
80
|
-
— {author}
|
|
81
|
-
</p>
|
|
82
|
-
</motion.div>
|
|
83
|
-
</div>
|
|
84
|
-
);
|
|
85
|
-
};
|
|
86
|
-
|
|
87
|
-
const Testimonials: React.FC<TestimonialsProps> = ({ isMobile }) => {
|
|
88
|
-
const [selectedTestimonial, setSelectedTestimonial] = useState<{
|
|
89
|
-
quote: string;
|
|
90
|
-
author: string;
|
|
91
|
-
} | null>(null);
|
|
92
|
-
const testimonialRef = useRef<HTMLDivElement>(null);
|
|
93
|
-
|
|
94
|
-
useEffect(() => {
|
|
95
|
-
const event = new CustomEvent("modalStateChange", {
|
|
96
|
-
detail: { isOpen: !!selectedTestimonial },
|
|
97
|
-
});
|
|
98
|
-
window.dispatchEvent(event);
|
|
99
|
-
}, [selectedTestimonial]);
|
|
100
|
-
|
|
101
|
-
const sectionVariants = {
|
|
102
|
-
hidden: { opacity: 0, y: 20 },
|
|
103
|
-
visible: {
|
|
104
|
-
opacity: 1,
|
|
105
|
-
y: 0,
|
|
106
|
-
transition: { duration: isMobile ? 0.3 : 0.5, ease: "easeOut" },
|
|
107
|
-
},
|
|
108
|
-
};
|
|
109
|
-
|
|
110
|
-
const cardVariants = {
|
|
111
|
-
hidden: { opacity: 0, y: 20 },
|
|
112
|
-
visible: {
|
|
113
|
-
opacity: 1,
|
|
114
|
-
y: 0,
|
|
115
|
-
transition: { duration: isMobile ? 0.3 : 0.5, ease: "easeOut" },
|
|
116
|
-
},
|
|
117
|
-
hover: {
|
|
118
|
-
scale: isMobile ? 1 : 1.05,
|
|
119
|
-
transition: { duration: 0.2, ease: "easeOut" },
|
|
120
|
-
},
|
|
121
|
-
};
|
|
122
|
-
|
|
123
|
-
const itemVariants = {
|
|
124
|
-
hidden: { opacity: 0, y: 10 },
|
|
125
|
-
visible: {
|
|
126
|
-
opacity: 1,
|
|
127
|
-
y: 0,
|
|
128
|
-
transition: { duration: isMobile ? 0.3 : 0.5, ease: "easeOut" },
|
|
129
|
-
},
|
|
130
|
-
};
|
|
131
|
-
|
|
132
|
-
const scrollToTestimonials = () => {
|
|
133
|
-
if (testimonialRef.current) {
|
|
134
|
-
const headerOffset = 290;
|
|
135
|
-
const elementPosition = testimonialRef.current.getBoundingClientRect().top + window.pageYOffset;
|
|
136
|
-
window.scrollTo({
|
|
137
|
-
top: elementPosition - headerOffset,
|
|
138
|
-
behavior: "smooth",
|
|
139
|
-
});
|
|
140
|
-
}
|
|
141
|
-
};
|
|
142
|
-
|
|
143
|
-
return (
|
|
144
|
-
<div className={`pt-12 px-12 pb-24 bg-[#1B2743] ${isMobile ? "pt-32" : "pt-12"} pb-12`}>
|
|
145
|
-
<style jsx>{`
|
|
146
|
-
@import url("https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700;800;900&display=swap");
|
|
147
|
-
|
|
148
|
-
:root {
|
|
149
|
-
--jubilee: #F47C7C;
|
|
150
|
-
--natural-white: #F7F7F7;
|
|
151
|
-
--caviar: #2D2D2D;
|
|
152
|
-
--storm-cloud: #4A636E;
|
|
153
|
-
--modern-purple: #D946EF;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
* {
|
|
157
|
-
font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
.glassmorphism {
|
|
161
|
-
background: rgba(255, 255, 255, 0.1);
|
|
162
|
-
backdrop-filter: blur(12px);
|
|
163
|
-
-webkit-backdrop-filter: blur(12px);
|
|
164
|
-
border: 1px solid rgba(255, 255, 255, 0.2);
|
|
165
|
-
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.08);
|
|
166
|
-
transition: transform 0.2s ease-out, box-shadow 0.2s ease-out;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
@supports not (backdrop-filter: blur(12px)) {
|
|
170
|
-
.glassmorphism {
|
|
171
|
-
background: rgba(255, 255, 255, 0.3);
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
.glassmorphism:hover {
|
|
176
|
-
box-shadow: 0 12px 32px rgba(0, 0, 0, 0.12);
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
.line-clamp-8 {
|
|
180
|
-
display: -webkit-box;
|
|
181
|
-
-webkit-line-clamp: 8;
|
|
182
|
-
-webkit-box-orient: vertical;
|
|
183
|
-
overflow: hidden;
|
|
184
|
-
}
|
|
185
|
-
`}</style>
|
|
186
|
-
{!isMobile && (
|
|
187
|
-
<div className="flex justify-center">
|
|
188
|
-
<button
|
|
189
|
-
onClick={scrollToTestimonials}
|
|
190
|
-
className="relative mb-72 transition-all duration-300 hover:scale-110"
|
|
191
|
-
style={{ top: "-20px" }}
|
|
192
|
-
aria-label="Scroll down"
|
|
193
|
-
>
|
|
194
|
-
<svg
|
|
195
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
196
|
-
width="120"
|
|
197
|
-
height="60"
|
|
198
|
-
viewBox="0 0 24 24"
|
|
199
|
-
fill="none"
|
|
200
|
-
stroke="currentColor"
|
|
201
|
-
strokeWidth="2"
|
|
202
|
-
strokeLinecap="round"
|
|
203
|
-
strokeLinejoin="round"
|
|
204
|
-
className="text-white transition-all duration-300"
|
|
205
|
-
>
|
|
206
|
-
<polyline points="4 8 12 16 20 8" />
|
|
207
|
-
</svg>
|
|
208
|
-
</button>
|
|
209
|
-
</div>
|
|
210
|
-
)}
|
|
211
|
-
<motion.div
|
|
212
|
-
ref={testimonialRef}
|
|
213
|
-
variants={sectionVariants}
|
|
214
|
-
initial="hidden"
|
|
215
|
-
animate="visible"
|
|
216
|
-
>
|
|
217
|
-
<motion.h2
|
|
218
|
-
variants={itemVariants}
|
|
219
|
-
className="text-4xl sm:text-5xl font-extrabold mb-16 text-white/90 text-center tracking-tight drop-shadow-md uppercase"
|
|
220
|
-
>
|
|
221
|
-
Testimonials
|
|
222
|
-
</motion.h2>
|
|
223
|
-
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8 sm:gap-10 lg:gap-12 min-h-fit items-stretch">
|
|
224
|
-
{[
|
|
225
|
-
{
|
|
226
|
-
quote: `Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.`,
|
|
227
|
-
author: "Susan H.",
|
|
228
|
-
},
|
|
229
|
-
{
|
|
230
|
-
quote: "Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.",
|
|
231
|
-
author: "Pam S.",
|
|
232
|
-
},
|
|
233
|
-
{
|
|
234
|
-
quote: "Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.",
|
|
235
|
-
author: "Stan K.",
|
|
236
|
-
},
|
|
237
|
-
{
|
|
238
|
-
quote: "Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.",
|
|
239
|
-
author: "Vicki M.",
|
|
240
|
-
},
|
|
241
|
-
{
|
|
242
|
-
quote: "Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.",
|
|
243
|
-
author: "Deb G.",
|
|
244
|
-
},
|
|
245
|
-
{
|
|
246
|
-
quote:
|
|
247
|
-
"Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.",
|
|
248
|
-
author: "Annie T.",
|
|
249
|
-
},
|
|
250
|
-
{
|
|
251
|
-
quote: "Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.",
|
|
252
|
-
author: "Kristi K.",
|
|
253
|
-
image: "",
|
|
254
|
-
},
|
|
255
|
-
{
|
|
256
|
-
quote:
|
|
257
|
-
"Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.",
|
|
258
|
-
author: "Carol K.",
|
|
259
|
-
},
|
|
260
|
-
{
|
|
261
|
-
quote:
|
|
262
|
-
"Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.",
|
|
263
|
-
author: "Kate F.",
|
|
264
|
-
},
|
|
265
|
-
{
|
|
266
|
-
quote:
|
|
267
|
-
"Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.",
|
|
268
|
-
author: "Anita D.",
|
|
269
|
-
},
|
|
270
|
-
].map((testimonial, index) => (
|
|
271
|
-
<motion.div
|
|
272
|
-
key={index}
|
|
273
|
-
variants={cardVariants}
|
|
274
|
-
initial="hidden"
|
|
275
|
-
animate="visible"
|
|
276
|
-
whileHover="hover"
|
|
277
|
-
className="flex"
|
|
278
|
-
>
|
|
279
|
-
<div
|
|
280
|
-
className="mb-16 overflow-hidden bg-white/30 backdrop-blur-lg border border-white/10 shadow-lg rounded-2xl w-full h-[500px] flex flex-col items-center justify-between"
|
|
281
|
-
style={{
|
|
282
|
-
backgroundImage: testimonial.image ? `url(${testimonial.image})` : "none",
|
|
283
|
-
backgroundSize: "contain",
|
|
284
|
-
backgroundPosition: "center",
|
|
285
|
-
backgroundRepeat: "no-repeat",
|
|
286
|
-
}}
|
|
287
|
-
role="region"
|
|
288
|
-
aria-label="Testimonial"
|
|
289
|
-
>
|
|
290
|
-
<div className={`flex-1 flex flex-col justify-center w-full bg-black/40 rounded-2xl ${isMobile ? "p-4" : "p-12"}`}>
|
|
291
|
-
<div className="flex flex-col items-center justify-center h-full">
|
|
292
|
-
<p
|
|
293
|
-
className="text-white text-base sm:text-lg font-medium italic mb-2 text-center content-text line-clamp-8"
|
|
294
|
-
>
|
|
295
|
-
“{testimonial.quote}”
|
|
296
|
-
</p>
|
|
297
|
-
<p
|
|
298
|
-
className="text-white/80 text-sm sm:text-base font-medium text-center content-text"
|
|
299
|
-
style={{
|
|
300
|
-
textShadow: `
|
|
301
|
-
-1px -1px 0 #000,
|
|
302
|
-
1px -1px 0 #000,
|
|
303
|
-
-1px 1px 0 #000,
|
|
304
|
-
1px 1px 0 #000,
|
|
305
|
-
0 2px 4px rgba(0, 0, 0, 0.4)
|
|
306
|
-
`,
|
|
307
|
-
}}
|
|
308
|
-
>
|
|
309
|
-
— {testimonial.author}
|
|
310
|
-
</p>
|
|
311
|
-
</div>
|
|
312
|
-
<button
|
|
313
|
-
onClick={() => setSelectedTestimonial({ quote: testimonial.quote, author: testimonial.author })}
|
|
314
|
-
className="mt-4 text-white bg-[#4a90e2] hover:bg-[#5c9de3] rounded-full px-4 py-2 text-sm font-semibold transition-colors duration-200 self-center"
|
|
315
|
-
>
|
|
316
|
-
Read More
|
|
317
|
-
</button>
|
|
318
|
-
</div>
|
|
319
|
-
</div>
|
|
320
|
-
</motion.div>
|
|
321
|
-
))}
|
|
322
|
-
</div>
|
|
323
|
-
<TestimonialModal
|
|
324
|
-
isOpen={!!selectedTestimonial}
|
|
325
|
-
onClose={() => setSelectedTestimonial(null)}
|
|
326
|
-
quote={selectedTestimonial?.quote || ""}
|
|
327
|
-
author={selectedTestimonial?.author || ""}
|
|
328
|
-
isMobile={isMobile}
|
|
329
|
-
/>
|
|
330
|
-
</motion.div>
|
|
331
|
-
</div>
|
|
332
|
-
);
|
|
333
|
-
};
|
|
334
|
-
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { motion } from "framer-motion";
|
|
4
|
+
import { useRef, useState, useEffect } from "react";
|
|
5
|
+
|
|
6
|
+
interface TestimonialModalProps {
|
|
7
|
+
isOpen: boolean;
|
|
8
|
+
onClose: () => void;
|
|
9
|
+
quote: string;
|
|
10
|
+
author: string;
|
|
11
|
+
isMobile: boolean;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
interface TestimonialsProps {
|
|
15
|
+
isMobile: boolean;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const TestimonialModal: React.FC<TestimonialModalProps> = ({ isOpen, onClose, quote, author, isMobile }) => {
|
|
19
|
+
const modalRef = useRef<HTMLDivElement>(null);
|
|
20
|
+
|
|
21
|
+
const handleClickOutside = (event: React.MouseEvent<HTMLDivElement>) => {
|
|
22
|
+
if (modalRef.current && !modalRef.current.contains(event.target as Node)) {
|
|
23
|
+
onClose();
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
useEffect(() => {
|
|
28
|
+
if (isOpen) {
|
|
29
|
+
const firstFocusable = modalRef.current?.querySelector("button") as HTMLElement;
|
|
30
|
+
firstFocusable?.focus();
|
|
31
|
+
document.body.style.overflow = "hidden";
|
|
32
|
+
} else {
|
|
33
|
+
document.body.style.overflow = "auto";
|
|
34
|
+
}
|
|
35
|
+
return () => {
|
|
36
|
+
document.body.style.overflow = "auto";
|
|
37
|
+
};
|
|
38
|
+
}, [isOpen]);
|
|
39
|
+
|
|
40
|
+
if (!isOpen) return null;
|
|
41
|
+
|
|
42
|
+
return (
|
|
43
|
+
<div
|
|
44
|
+
className="fixed inset-0 bg-black/50 flex items-center justify-center z-[10000] p-4"
|
|
45
|
+
onClick={handleClickOutside}
|
|
46
|
+
aria-modal="true"
|
|
47
|
+
role="dialog"
|
|
48
|
+
>
|
|
49
|
+
<motion.div
|
|
50
|
+
ref={modalRef}
|
|
51
|
+
initial={{ opacity: 0, scale: 0.95 }}
|
|
52
|
+
animate={{ opacity: 1, scale: 1 }}
|
|
53
|
+
exit={{ opacity: 0, scale: 0.95 }}
|
|
54
|
+
className="bg-white rounded-2xl p-6 sm:p-8 max-w-lg w-full overflow-y-auto relative glassmorphism"
|
|
55
|
+
style={{ maxHeight: isMobile ? "50vh" : "80vh" }}
|
|
56
|
+
>
|
|
57
|
+
<button
|
|
58
|
+
onClick={onClose}
|
|
59
|
+
className="absolute top-4 right-4 text-gray-500 hover:text-gray-700"
|
|
60
|
+
aria-label="Close testimonial modal"
|
|
61
|
+
>
|
|
62
|
+
<svg
|
|
63
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
64
|
+
width="24"
|
|
65
|
+
height="24"
|
|
66
|
+
viewBox="0 0 24 24"
|
|
67
|
+
fill="none"
|
|
68
|
+
stroke="currentColor"
|
|
69
|
+
strokeWidth="2"
|
|
70
|
+
strokeLinecap="round"
|
|
71
|
+
strokeLinejoin="round"
|
|
72
|
+
>
|
|
73
|
+
<path d="M18 6L6 18M6 6l12 12" />
|
|
74
|
+
</svg>
|
|
75
|
+
</button>
|
|
76
|
+
<p className="text-[#2D2D2D] text-lg sm:text-xl font-medium italic mb-4">
|
|
77
|
+
“{quote}”
|
|
78
|
+
</p>
|
|
79
|
+
<p className="text-[#2D2D2D] text-base sm:text-lg font-medium text-right">
|
|
80
|
+
— {author}
|
|
81
|
+
</p>
|
|
82
|
+
</motion.div>
|
|
83
|
+
</div>
|
|
84
|
+
);
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
const Testimonials: React.FC<TestimonialsProps> = ({ isMobile }) => {
|
|
88
|
+
const [selectedTestimonial, setSelectedTestimonial] = useState<{
|
|
89
|
+
quote: string;
|
|
90
|
+
author: string;
|
|
91
|
+
} | null>(null);
|
|
92
|
+
const testimonialRef = useRef<HTMLDivElement>(null);
|
|
93
|
+
|
|
94
|
+
useEffect(() => {
|
|
95
|
+
const event = new CustomEvent("modalStateChange", {
|
|
96
|
+
detail: { isOpen: !!selectedTestimonial },
|
|
97
|
+
});
|
|
98
|
+
window.dispatchEvent(event);
|
|
99
|
+
}, [selectedTestimonial]);
|
|
100
|
+
|
|
101
|
+
const sectionVariants = {
|
|
102
|
+
hidden: { opacity: 0, y: 20 },
|
|
103
|
+
visible: {
|
|
104
|
+
opacity: 1,
|
|
105
|
+
y: 0,
|
|
106
|
+
transition: { duration: isMobile ? 0.3 : 0.5, ease: "easeOut" },
|
|
107
|
+
},
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
const cardVariants = {
|
|
111
|
+
hidden: { opacity: 0, y: 20 },
|
|
112
|
+
visible: {
|
|
113
|
+
opacity: 1,
|
|
114
|
+
y: 0,
|
|
115
|
+
transition: { duration: isMobile ? 0.3 : 0.5, ease: "easeOut" },
|
|
116
|
+
},
|
|
117
|
+
hover: {
|
|
118
|
+
scale: isMobile ? 1 : 1.05,
|
|
119
|
+
transition: { duration: 0.2, ease: "easeOut" },
|
|
120
|
+
},
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
const itemVariants = {
|
|
124
|
+
hidden: { opacity: 0, y: 10 },
|
|
125
|
+
visible: {
|
|
126
|
+
opacity: 1,
|
|
127
|
+
y: 0,
|
|
128
|
+
transition: { duration: isMobile ? 0.3 : 0.5, ease: "easeOut" },
|
|
129
|
+
},
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
const scrollToTestimonials = () => {
|
|
133
|
+
if (testimonialRef.current) {
|
|
134
|
+
const headerOffset = 290;
|
|
135
|
+
const elementPosition = testimonialRef.current.getBoundingClientRect().top + window.pageYOffset;
|
|
136
|
+
window.scrollTo({
|
|
137
|
+
top: elementPosition - headerOffset,
|
|
138
|
+
behavior: "smooth",
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
return (
|
|
144
|
+
<div className={`pt-12 px-12 pb-24 bg-[#1B2743] ${isMobile ? "pt-32" : "pt-12"} pb-12`}>
|
|
145
|
+
<style jsx>{`
|
|
146
|
+
@import url("https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700;800;900&display=swap");
|
|
147
|
+
|
|
148
|
+
:root {
|
|
149
|
+
--jubilee: #F47C7C;
|
|
150
|
+
--natural-white: #F7F7F7;
|
|
151
|
+
--caviar: #2D2D2D;
|
|
152
|
+
--storm-cloud: #4A636E;
|
|
153
|
+
--modern-purple: #D946EF;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
* {
|
|
157
|
+
font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
.glassmorphism {
|
|
161
|
+
background: rgba(255, 255, 255, 0.1);
|
|
162
|
+
backdrop-filter: blur(12px);
|
|
163
|
+
-webkit-backdrop-filter: blur(12px);
|
|
164
|
+
border: 1px solid rgba(255, 255, 255, 0.2);
|
|
165
|
+
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.08);
|
|
166
|
+
transition: transform 0.2s ease-out, box-shadow 0.2s ease-out;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
@supports not (backdrop-filter: blur(12px)) {
|
|
170
|
+
.glassmorphism {
|
|
171
|
+
background: rgba(255, 255, 255, 0.3);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
.glassmorphism:hover {
|
|
176
|
+
box-shadow: 0 12px 32px rgba(0, 0, 0, 0.12);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
.line-clamp-8 {
|
|
180
|
+
display: -webkit-box;
|
|
181
|
+
-webkit-line-clamp: 8;
|
|
182
|
+
-webkit-box-orient: vertical;
|
|
183
|
+
overflow: hidden;
|
|
184
|
+
}
|
|
185
|
+
`}</style>
|
|
186
|
+
{!isMobile && (
|
|
187
|
+
<div className="flex justify-center">
|
|
188
|
+
<button
|
|
189
|
+
onClick={scrollToTestimonials}
|
|
190
|
+
className="relative mb-72 transition-all duration-300 hover:scale-110"
|
|
191
|
+
style={{ top: "-20px" }}
|
|
192
|
+
aria-label="Scroll down"
|
|
193
|
+
>
|
|
194
|
+
<svg
|
|
195
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
196
|
+
width="120"
|
|
197
|
+
height="60"
|
|
198
|
+
viewBox="0 0 24 24"
|
|
199
|
+
fill="none"
|
|
200
|
+
stroke="currentColor"
|
|
201
|
+
strokeWidth="2"
|
|
202
|
+
strokeLinecap="round"
|
|
203
|
+
strokeLinejoin="round"
|
|
204
|
+
className="text-white transition-all duration-300"
|
|
205
|
+
>
|
|
206
|
+
<polyline points="4 8 12 16 20 8" />
|
|
207
|
+
</svg>
|
|
208
|
+
</button>
|
|
209
|
+
</div>
|
|
210
|
+
)}
|
|
211
|
+
<motion.div
|
|
212
|
+
ref={testimonialRef}
|
|
213
|
+
variants={sectionVariants}
|
|
214
|
+
initial="hidden"
|
|
215
|
+
animate="visible"
|
|
216
|
+
>
|
|
217
|
+
<motion.h2
|
|
218
|
+
variants={itemVariants}
|
|
219
|
+
className="text-4xl sm:text-5xl font-extrabold mb-16 text-white/90 text-center tracking-tight drop-shadow-md uppercase"
|
|
220
|
+
>
|
|
221
|
+
Testimonials
|
|
222
|
+
</motion.h2>
|
|
223
|
+
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8 sm:gap-10 lg:gap-12 min-h-fit items-stretch">
|
|
224
|
+
{[
|
|
225
|
+
{
|
|
226
|
+
quote: `Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.`,
|
|
227
|
+
author: "Susan H.",
|
|
228
|
+
},
|
|
229
|
+
{
|
|
230
|
+
quote: "Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.",
|
|
231
|
+
author: "Pam S.",
|
|
232
|
+
},
|
|
233
|
+
{
|
|
234
|
+
quote: "Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.",
|
|
235
|
+
author: "Stan K.",
|
|
236
|
+
},
|
|
237
|
+
{
|
|
238
|
+
quote: "Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.",
|
|
239
|
+
author: "Vicki M.",
|
|
240
|
+
},
|
|
241
|
+
{
|
|
242
|
+
quote: "Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.",
|
|
243
|
+
author: "Deb G.",
|
|
244
|
+
},
|
|
245
|
+
{
|
|
246
|
+
quote:
|
|
247
|
+
"Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.",
|
|
248
|
+
author: "Annie T.",
|
|
249
|
+
},
|
|
250
|
+
{
|
|
251
|
+
quote: "Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.",
|
|
252
|
+
author: "Kristi K.",
|
|
253
|
+
image: "",
|
|
254
|
+
},
|
|
255
|
+
{
|
|
256
|
+
quote:
|
|
257
|
+
"Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.",
|
|
258
|
+
author: "Carol K.",
|
|
259
|
+
},
|
|
260
|
+
{
|
|
261
|
+
quote:
|
|
262
|
+
"Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.",
|
|
263
|
+
author: "Kate F.",
|
|
264
|
+
},
|
|
265
|
+
{
|
|
266
|
+
quote:
|
|
267
|
+
"Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.",
|
|
268
|
+
author: "Anita D.",
|
|
269
|
+
},
|
|
270
|
+
].map((testimonial, index) => (
|
|
271
|
+
<motion.div
|
|
272
|
+
key={index}
|
|
273
|
+
variants={cardVariants}
|
|
274
|
+
initial="hidden"
|
|
275
|
+
animate="visible"
|
|
276
|
+
whileHover="hover"
|
|
277
|
+
className="flex"
|
|
278
|
+
>
|
|
279
|
+
<div
|
|
280
|
+
className="mb-16 overflow-hidden bg-white/30 backdrop-blur-lg border border-white/10 shadow-lg rounded-2xl w-full h-[500px] flex flex-col items-center justify-between"
|
|
281
|
+
style={{
|
|
282
|
+
backgroundImage: testimonial.image ? `url(${testimonial.image})` : "none",
|
|
283
|
+
backgroundSize: "contain",
|
|
284
|
+
backgroundPosition: "center",
|
|
285
|
+
backgroundRepeat: "no-repeat",
|
|
286
|
+
}}
|
|
287
|
+
role="region"
|
|
288
|
+
aria-label="Testimonial"
|
|
289
|
+
>
|
|
290
|
+
<div className={`flex-1 flex flex-col justify-center w-full bg-black/40 rounded-2xl ${isMobile ? "p-4" : "p-12"}`}>
|
|
291
|
+
<div className="flex flex-col items-center justify-center h-full">
|
|
292
|
+
<p
|
|
293
|
+
className="text-white text-base sm:text-lg font-medium italic mb-2 text-center content-text line-clamp-8"
|
|
294
|
+
>
|
|
295
|
+
“{testimonial.quote}”
|
|
296
|
+
</p>
|
|
297
|
+
<p
|
|
298
|
+
className="text-white/80 text-sm sm:text-base font-medium text-center content-text"
|
|
299
|
+
style={{
|
|
300
|
+
textShadow: `
|
|
301
|
+
-1px -1px 0 #000,
|
|
302
|
+
1px -1px 0 #000,
|
|
303
|
+
-1px 1px 0 #000,
|
|
304
|
+
1px 1px 0 #000,
|
|
305
|
+
0 2px 4px rgba(0, 0, 0, 0.4)
|
|
306
|
+
`,
|
|
307
|
+
}}
|
|
308
|
+
>
|
|
309
|
+
— {testimonial.author}
|
|
310
|
+
</p>
|
|
311
|
+
</div>
|
|
312
|
+
<button
|
|
313
|
+
onClick={() => setSelectedTestimonial({ quote: testimonial.quote, author: testimonial.author })}
|
|
314
|
+
className="mt-4 text-white bg-[#4a90e2] hover:bg-[#5c9de3] rounded-full px-4 py-2 text-sm font-semibold transition-colors duration-200 self-center"
|
|
315
|
+
>
|
|
316
|
+
Read More
|
|
317
|
+
</button>
|
|
318
|
+
</div>
|
|
319
|
+
</div>
|
|
320
|
+
</motion.div>
|
|
321
|
+
))}
|
|
322
|
+
</div>
|
|
323
|
+
<TestimonialModal
|
|
324
|
+
isOpen={!!selectedTestimonial}
|
|
325
|
+
onClose={() => setSelectedTestimonial(null)}
|
|
326
|
+
quote={selectedTestimonial?.quote || ""}
|
|
327
|
+
author={selectedTestimonial?.author || ""}
|
|
328
|
+
isMobile={isMobile}
|
|
329
|
+
/>
|
|
330
|
+
</motion.div>
|
|
331
|
+
</div>
|
|
332
|
+
);
|
|
333
|
+
};
|
|
334
|
+
|
|
335
335
|
export default Testimonials;
|