229 lines
7.1 KiB
JavaScript
229 lines
7.1 KiB
JavaScript
'use client'
|
|
|
|
import * as Headless from '@headlessui/react'
|
|
import { ArrowLongRightIcon } from '@heroicons/react/20/solid'
|
|
import { clsx } from 'clsx'
|
|
import {
|
|
motion,
|
|
useMotionValueEvent,
|
|
useScroll,
|
|
useSpring,
|
|
} from 'framer-motion'
|
|
import { useCallback, useLayoutEffect, useRef, useState } from 'react'
|
|
import useMeasure from 'react-use-measure'
|
|
import { Container } from './Container'
|
|
import { Link } from './link'
|
|
import { Heading, Subheading, H2, P, PS, H3, H4 } from './text'
|
|
import { Button } from './Button'
|
|
|
|
const testimonials = [
|
|
{
|
|
img: '/images/veda1.jpg',
|
|
name: '0 - 6 Years Old',
|
|
title: 'From birth to age 6, we offer ECD programs that change lives forever.',
|
|
subtitle: 'A beautiful 50-meter dahabiya offering a tranquil and organic platform for personalized cruises.',
|
|
quote: 'VEDA 1',
|
|
href: '/phases/phase1',
|
|
},
|
|
{
|
|
img: '/images/veda2.jpg',
|
|
name: '6 - 15 Years Old',
|
|
title: 'Unlock the Potential of Youth with transformational learning experiences',
|
|
subtitle: 'An elegant 45-meter dahabiya, ideal for hosting larger groups, healing retreats, company getaways, and more.',
|
|
quote: 'VEDA 2',
|
|
href: '/phases/phase2',
|
|
},
|
|
{
|
|
img: '/images/veda3.jpg',
|
|
name: '15 - 25 Years Old',
|
|
title: 'Skills that Earn & Regenerate Vocational paths that equip young people to live with purpose.',
|
|
subtitle: 'A cozy 18-meter dahabiya offering a serene floating home, perfect for private groups seeking tranquility and comfort on the Nile.',
|
|
quote: 'VEDA 3',
|
|
href: '/phases/phase3',
|
|
},
|
|
{
|
|
img: '/images/veda4.jpg',
|
|
name: 'All Ages',
|
|
title: 'A unique portfolio of impact proven Community-led solutions worth implementing',
|
|
subtitle: 'A spaciou 55-meter dahabeya offering a serene retreat, perfect for bigger groups seeking tranquility and comfort on the Nile.',
|
|
quote: 'VEDA 4',
|
|
href: '/phases/phase4',
|
|
},
|
|
]
|
|
|
|
function TestimonialCard({
|
|
subtitle,
|
|
name,
|
|
title,
|
|
img,
|
|
href,
|
|
children,
|
|
bounds,
|
|
scrollX,
|
|
...props
|
|
}) {
|
|
let ref = useRef(null)
|
|
|
|
let computeOpacity = useCallback(() => {
|
|
let element = ref.current
|
|
if (!element || bounds.width === 0) return 1
|
|
|
|
let rect = element.getBoundingClientRect()
|
|
|
|
if (rect.left < bounds.left) {
|
|
let diff = bounds.left - rect.left
|
|
let percent = diff / rect.width
|
|
return Math.max(0.5, 1 - percent)
|
|
} else if (rect.right > bounds.right) {
|
|
let diff = rect.right - bounds.right
|
|
let percent = diff / rect.width
|
|
return Math.max(0.5, 1 - percent)
|
|
} else {
|
|
return 1
|
|
}
|
|
}, [ref, bounds.width, bounds.left, bounds.right])
|
|
|
|
let opacity = useSpring(computeOpacity(), {
|
|
stiffness: 154,
|
|
damping: 23,
|
|
})
|
|
|
|
useLayoutEffect(() => {
|
|
opacity.set(computeOpacity())
|
|
}, [computeOpacity, opacity])
|
|
|
|
useMotionValueEvent(scrollX, 'change', () => {
|
|
opacity.set(computeOpacity())
|
|
})
|
|
|
|
return (
|
|
<motion.div
|
|
ref={ref}
|
|
{...props}
|
|
className="w-72 shrink-0 snap-start scroll-ml-(--scroll-padding) bg-transparent overflow-hidden sm:w-80 lg:w-96"
|
|
style={{ aspectRatio: '360/460' }}
|
|
>
|
|
{/* Image Section */}
|
|
<div className="relative overflow-hidden" style={{ height: '75%' }}>
|
|
<img
|
|
alt=""
|
|
src={img}
|
|
className="w-full h-full object-cover"
|
|
/>
|
|
</div>
|
|
|
|
{/* Content Section Below Image */}
|
|
<div className="py-2 px-2" style={{ height: '25%' }}>
|
|
<blockquote>
|
|
<H4 className="">
|
|
{children}
|
|
</H4>
|
|
</blockquote>
|
|
|
|
<PS className="mb-2 text-xs lg:text-sm">
|
|
{subtitle}
|
|
</PS>
|
|
|
|
<Button
|
|
href={href}
|
|
variant="link"
|
|
color="darkgr"
|
|
className="text-xs"
|
|
>
|
|
Learn More
|
|
</Button>
|
|
</div>
|
|
</motion.div>
|
|
)
|
|
}
|
|
|
|
export function Boat() {
|
|
let scrollRef = useRef(null)
|
|
let { scrollX } = useScroll({ container: scrollRef })
|
|
let [setReferenceWindowRef, bounds] = useMeasure()
|
|
let [activeIndex, setActiveIndex] = useState(0)
|
|
let [scrollProgress, setScrollProgress] = useState(0)
|
|
|
|
useMotionValueEvent(scrollX, 'change', (x) => {
|
|
if (scrollRef.current && scrollRef.current.children[0]) {
|
|
setActiveIndex(Math.floor(x / scrollRef.current.children[0].clientWidth))
|
|
|
|
// Calculate scroll progress
|
|
const maxScroll = scrollRef.current.scrollWidth - scrollRef.current.clientWidth
|
|
const progress = maxScroll > 0 ? (x / maxScroll) * 100 : 0
|
|
setScrollProgress(Math.min(100, Math.max(0, progress)))
|
|
}
|
|
})
|
|
|
|
function scrollTo(index) {
|
|
let gap = 32
|
|
let width = scrollRef.current.children[0].offsetWidth
|
|
scrollRef.current.scrollTo({ left: (width + gap) * index })
|
|
}
|
|
|
|
return (
|
|
<div className="pt-12 pb-12 bg-transparent">
|
|
<Container>
|
|
<div className="grid grid-cols-1 lg:grid-cols-12 gap-8 lg:gap-12 items-start">
|
|
{/* Text Content - Left Side */}
|
|
<div ref={setReferenceWindowRef} className="lg:col-span-4">
|
|
<H2 className="">
|
|
Dahabiyas
|
|
</H2>
|
|
<PS className="">
|
|
Discover peaceful platforms where every detail ensures a truly memorable stay.
|
|
</PS >
|
|
<PS className="mb-6">
|
|
Our fleet of traditional dahabiyas combines authentic Nile heritage with modern comfort, offering intimate sailing experiences that connect you with Egypt's timeless river culture.
|
|
</PS>
|
|
<Button href="/dahabiyas" variant="link" color="darkgr" className="mt-6">
|
|
Learn More
|
|
</Button>
|
|
</div>
|
|
|
|
{/* Carousel - Right Side */}
|
|
<div className="lg:col-span-8">
|
|
<div
|
|
ref={scrollRef}
|
|
className={clsx([
|
|
'flex gap-8 pl-6 pr-6 lg:pl-0 lg:pr-8',
|
|
'[scrollbar-width:none] [&::-webkit-scrollbar]:hidden',
|
|
'snap-x snap-mandatory overflow-x-scroll overscroll-x-contain scroll-smooth',
|
|
'pb-8',
|
|
])}
|
|
>
|
|
{testimonials.map(({ img, name, title, quote, href, subtitle }, testimonialIndex) => (
|
|
<TestimonialCard
|
|
key={testimonialIndex}
|
|
subtitle={subtitle}
|
|
name={name}
|
|
title={title}
|
|
href={href}
|
|
img={img}
|
|
bounds={bounds}
|
|
scrollX={scrollX}
|
|
onClick={() => scrollTo(testimonialIndex)}
|
|
>
|
|
{quote}
|
|
</TestimonialCard>
|
|
))}
|
|
<div className="w-8 shrink-0" />
|
|
</div>
|
|
|
|
{/* Progress Bar */}
|
|
<div className="mt-6 mx-6 lg:mx-0 flex justify-center">
|
|
<div className="w-52 bg-gray-400 rounded-full h-0.5 relative">
|
|
<div
|
|
className="bg-darkgr-900 h-0.5 rounded-full transition-all duration-300 ease-out"
|
|
style={{ width: `${scrollProgress}%` }}
|
|
/>
|
|
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</Container>
|
|
</div>
|
|
)
|
|
}
|