Files
www_veda_2025/src/components/Boat.jsx
2025-08-25 16:36:58 +02:00

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>
)
}