merge: merge main into development_fix keeping development_fix versions

- Keep all development_fix build artifacts and configurations
- Resolve conflicts by preferring development_fix versions
- Maintain existing development_fix branch functionality
This commit is contained in:
2025-08-18 23:47:27 +02:00
145 changed files with 1535 additions and 169 deletions

197
src/components/Boat.jsx Normal file
View File

@@ -0,0 +1,197 @@
'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 } from './text'
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 dahabeya offering a serene floating home experience, 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 cozy 55-meter dahabeya offering a serene floating home experience, perfect for smaller 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}
style={{ opacity }}
{...props}
className="w-72 shrink-0 snap-start scroll-ml-(--scroll-padding) bg-white rounded-3xl shadow-lg overflow-hidden sm:w-96"
>
{/* Image Section */}
<div className="relative aspect-square overflow-hidden">
<img
alt=""
src={img}
className="w-full h-full object-cover"
/>
</div>
{/* Content Section Below Image */}
<div className="p-6">
<blockquote>
<p className="text-2xl font-bold text-gray-900 lg:text-3xl">
{children}
</p>
</blockquote>
<p className="mt-4 text-sm text-gray-600 leading-6">
{subtitle}
</p>
<Link
href={href}
className="mt-6 inline-flex items-center gap-2 text-sm font-medium text-gold-600 hover:text-gold-700"
>
Learn More
<ArrowLongRightIcon className="h-4 w-4" />
</Link>
</div>
</motion.div>
)
}
export function Boat() {
let scrollRef = useRef(null)
let { scrollX } = useScroll({ container: scrollRef })
let [setReferenceWindowRef, bounds] = useMeasure()
let [activeIndex, setActiveIndex] = useState(0)
useMotionValueEvent(scrollX, 'change', (x) => {
if (scrollRef.current && scrollRef.current.children[0]) {
setActiveIndex(Math.floor(x / scrollRef.current.children[0].clientWidth))
}
})
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-24">
<Container>
<div ref={setReferenceWindowRef}>
<h2 className="text-base font-semibold leading-7 text-gold-600">OUR OFFERS</h2>
<h2 className="font-display mt-2 text-3xl font-bold tracking-tight text-gray-800 sm:text-4xl">
VEDA DAHABIYAS
</h2>
<p className="mt-2 text-lg tracking-wide font-normal leading-8 text-gray-600">
Discover peaceful platforms where every detail ensures a truly memorable stay. 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. Each vessel is carefully maintained and uniquely designed to provide the perfect setting for relaxation, reflection, and genuine connection with the magnificent Nile landscape.
</p>
</div>
</Container>
<div
ref={scrollRef}
className={clsx([
'mt-16 flex gap-8 pl-6 pr-6 lg:pl-8',
'[scrollbar-width:none] [&::-webkit-scrollbar]:hidden',
'snap-x snap-mandatory overflow-x-auto 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>
</div>
)
}

View File

@@ -3,7 +3,7 @@ import Image from 'next/image'
import { Button } from '@/components/Button'
import { Container } from '@/components/Container'
import backgroundImage from '@/images/background-call-to-action.jpg'
import logoVeda from '@/images/logos/veda.svg'
import logoVeda2 from '@/images/logos/veda_icon2.svg'
export function CallToAction() {
return (
@@ -27,7 +27,7 @@ export function CallToAction() {
>
{[
[
{ name: 'veda', logo: logoVeda },
{ name: 'veda', logo: logoVeda2 },
],
].map((group, groupIndex) => (
<li key={groupIndex}>
@@ -47,7 +47,7 @@ export function CallToAction() {
<h2 className="font-display lg:text-3xl text-xl tracking-tight text-white pt-4 lg:pt-6">
VEDA provides an unparalleled cruise experience on the Nile, blending authenticity with sophistication. Enjoy private journeys featuring organic cuisine, hollistic activities, and a dedicated, warm-hearted crew.
</h2>
<p className="mt-4 text-xl lg:text-2xl italic tracking-tight text-gold-200">
<p className="mt-4 text-xl lg:text-2xl italic tracking-tight text-gold-100">
Discover a cruise like no other with us on the Nile.
</p>
</div>

View File

@@ -3,7 +3,7 @@ import Image from 'next/image'
import { Button } from '@/components/Button'
import { Container } from '@/components/Container'
import backgroundImage from '@/images/background-call-to-action4.png'
import logoVeda from '@/images/logos/logo_name.svg'
import logoVeda from '@/images/logos/veda_logo2.svg'
export function CallToAction3() {
return (
@@ -33,7 +33,7 @@ export function CallToAction3() {
<li key={groupIndex}>
<ul
role="list"
className="flex flex-col items-center gap-y-2 sm:flex-row sm:gap-x-12 sm:gap-y-0"
className="flex flex-col items-center gap-y-2 sm:flex-row sm:gap-x-12 sm:gap-y-0 "
>
{group.map((company) => (
<li key={company.name} className="flex">

View File

@@ -0,0 +1,253 @@
'use client'
import { Fragment, useState } from 'react'
import {
Dialog,
DialogBackdrop,
DialogPanel,
Disclosure,
DisclosureButton,
DisclosurePanel,
Popover,
PopoverButton,
PopoverGroup,
PopoverPanel,
Tab,
TabGroup,
TabList,
TabPanel,
TabPanels,
} from '@headlessui/react'
import {
Bars3Icon,
HeartIcon,
MagnifyingGlassIcon,
MinusIcon,
PlusIcon,
ShoppingBagIcon,
UserIcon,
XMarkIcon,
} from '@heroicons/react/24/outline'
import { StarIcon } from '@heroicons/react/20/solid'
const product = {
name: 'Community Buildings',
images: [
{
id: 1,
name: 'Community Space 1',
src: '/images/community1.jpg',
alt: 'Community gathering space with traditional design',
},
{
id: 2,
name: 'Community Space 2',
src: '/images/community2.jpg',
alt: 'Interior view of community building',
},
{
id: 3,
name: 'Community Space 3',
src: '/images/community3.jpg',
alt: 'Community space with seating arrangements',
},
{
id: 4,
name: 'Community Space 4',
src: '/images/community4.jpg',
alt: 'Community building exterior and surroundings',
},
],
colors: [
{ id: 'washed-black', name: 'Washed Black', classes: 'bg-gray-700 checked:outline-gray-700' },
{ id: 'white', name: 'White', classes: 'bg-white checked:outline-gray-400' },
{ id: 'washed-gray', name: 'Washed Gray', classes: 'bg-gray-500 checked:outline-gray-500' },
],
description: `
<p>Community building holds significant value by fostering a sense of belonging, providing social and emotional support, and promoting collective action. It enhances social capital, encourages collaboration. Human to human connection become more and more important as new technological times require us to collaborate more on a peer to peer level and connect on different levels with each other.</p>
`,
details: [
{
name: 'Features',
items: [
'Tailored itineraries that not only traverse the Nile but also include excursions to monumental sites like the temples of Luxor and the iconic pyramid to create life lasting bonds and inspire to integrate history while creating the future.',
'A comprehensive wellness approach integrating yoga, meditation, massage, and nutritious cuisine crafted by our onboard chef, complemented by wellness workshops aimed at holistic health.',
'Personal growth sessions led by experienced coaches, offering mentoring and guidance to foster self-discovery and personal development.',
],
},
{
name: 'Benefits',
items: [
'Enhanced self-awareness and rejuvenation within a nurturing environment, surrounded by the timeless beauty of the Nile.',
'Building connections with like-minded individuals in an intimate setting, ideal for forging community ties and shared transformative experiences.',
'Machine wash interior dividers',
'A balanced mix of relaxation, adventure, and cultural immersion, fostering a profound sense of well-being and enrichment.',
],
},
],
}
const relatedProducts = [
{
id: 1,
name: 'Zip Tote Basket',
color: 'White and black',
href: '#',
imageSrc: 'https://tailwindcss.com/plus-assets/img/ecommerce-images/product-page-03-related-product-01.jpg',
imageAlt: 'Front of zip tote bag with white canvas, black canvas straps and handle, and black zipper pulls.',
price: '$140',
},
{
id: 2,
name: 'Zip High Wall Tote',
color: 'White and blue',
href: '#',
imageSrc: 'https://tailwindcss.com/plus-assets/img/ecommerce-images/product-page-03-related-product-02.jpg',
imageAlt: 'Front of zip tote bag with white canvas, blue canvas straps and handle, and front zipper pocket.',
price: '$150',
},
{
id: 3,
name: 'Halfsize Tote',
color: 'Clay',
href: '#',
imageSrc: 'https://tailwindcss.com/plus-assets/img/ecommerce-images/product-page-03-related-product-03.jpg',
imageAlt: 'Front of tote with monochrome natural canvas body, straps, roll top, and handles.',
price: '$210',
},
{
id: 4,
name: 'High Wall Tote',
color: 'Black and orange',
href: '#',
imageSrc: 'https://tailwindcss.com/plus-assets/img/ecommerce-images/product-page-03-related-product-04.jpg',
imageAlt: 'Front of zip tote bag with black canvas, black handles, and orange drawstring top.',
price: '$210',
},
]
function classNames(...classes) {
return classes.filter(Boolean).join(' ')
}
export function Community() {
const [open, setOpen] = useState(false)
return (
<div className="pb-24">
<main className="mx-auto max-w-7xl sm:px-6 sm:pt-16 lg:px-8">
<div className="mx-auto max-w-2xl lg:max-w-none">
{/* Product */}
<div className="lg:grid lg:grid-cols-2 lg:items-start lg:gap-x-8">
{/* Image gallery */}
<TabGroup className="flex flex-col-reverse">
{/* Image selector */}
<div className="mx-auto mt-6 hidden w-full max-w-2xl sm:block lg:max-w-none">
<TabList className="grid grid-cols-4 gap-6">
{product.images.map((image) => (
<Tab
key={image.id}
className="group relative flex h-24 cursor-pointer items-center justify-center rounded-md bg-white text-sm font-medium text-gray-900 uppercase hover:bg-gray-50 focus:ring-3 focus:ring-indigo-500/50 focus:ring-offset-4 focus:outline-hidden"
>
<span className="sr-only">{image.name}</span>
<span className="absolute inset-0 overflow-hidden rounded-md">
<img alt="" src={image.src} className="size-full object-cover" />
</span>
<span
aria-hidden="true"
className="pointer-events-none absolute inset-0 rounded-md ring-2 ring-transparent ring-offset-2 group-data-selected:ring-indigo-500"
/>
</Tab>
))}
</TabList>
</div>
<TabPanels>
{product.images.map((image) => (
<TabPanel key={image.id}>
<img alt={image.alt} src={image.src} className="aspect-square w-full object-cover sm:rounded-lg" />
</TabPanel>
))}
</TabPanels>
</TabGroup>
{/* Product info */}
<div className="mt-10 px-4 sm:mt-16 sm:px-0 lg:mt-0">
<h1 className="text-3xl font-bold tracking-tight text-gray-900">{product.name}</h1>
<div className="mt-3">
<h2 className="sr-only">Product information</h2>
</div>
{/* Reviews */}
<div className="mt-3">
<h3 className="sr-only">Reviews</h3>
</div>
<div className="mt-6">
<h3 className="sr-only">Description</h3>
<div
dangerouslySetInnerHTML={{ __html: product.description }}
className="space-y-6 text-base text-gray-700"
/>
</div>
<section aria-labelledby="details-heading" className="mt-12">
<h2 id="details-heading" className="sr-only">
Additional details
</h2>
<div className="divide-y divide-gray-200 border-t border-gray-200">
{product.details.map((detail) => (
<Disclosure key={detail.name} as="div">
<h3>
<DisclosureButton className="group relative flex w-full items-center justify-between py-6 text-left">
<span className="text-sm font-medium text-gray-900 group-data-open:text-indigo-600">
{detail.name}
</span>
<span className="ml-6 flex items-center">
<PlusIcon
aria-hidden="true"
className="block size-6 text-gray-400 group-hover:text-gray-500 group-data-open:hidden"
/>
<MinusIcon
aria-hidden="true"
className="hidden size-6 text-indigo-400 group-hover:text-indigo-500 group-data-open:block"
/>
</span>
</DisclosureButton>
</h3>
<DisclosurePanel className="pb-6">
<ul
role="list"
className="list-disc space-y-1 pl-5 text-sm/6 text-gray-700 marker:text-gray-300"
>
{detail.items.map((item) => (
<li key={item} className="pl-2">
{item}
</li>
))}
</ul>
</DisclosurePanel>
</Disclosure>
))}
</div>
</section>
</div>
</div>
</div>
</main>
</div>
)
}

View File

@@ -0,0 +1,98 @@
'use client'
import { BuildingOffice2Icon, EnvelopeIcon } from '@heroicons/react/24/outline'
import { useState } from 'react'
import Image from 'next/image'
export function ContactHero() {
return (
<div className="relative isolate">
<div className="mx-auto grid max-w-7xl grid-cols-1 lg:grid-cols-2">
{/* Left container */}
<div className="relative px-6 pt-24 pb-20 lg:static lg:px-8 lg:py-32">
<div className="mx-auto max-w-xl lg:mx-0 lg:max-w-lg">
<div className="absolute inset-y-0 left-0 -z-10 w-full overflow-hidden bg-gray-100 ring-1 ring-gray-900/10 lg:w-1/2">
<svg
aria-hidden="true"
className="absolute inset-0 size-full mask-[radial-gradient(100%_100%_at_top_right,white,transparent)] stroke-gray-200"
>
<defs>
<pattern
x="100%"
y={-1}
id="83fd4e5a-9d52-42fc-97b6-718e5d7ee527"
width={200}
height={200}
patternUnits="userSpaceOnUse"
>
<path d="M130 200V.5M.5 .5H200" fill="none" />
</pattern>
</defs>
<rect width="100%" height="100%" strokeWidth={0} className="fill-white" />
<svg x="100%" y={-1} className="overflow-visible fill-gray-50">
<path d="M-470.5 0h201v201h-201Z" strokeWidth={0} />
</svg>
<rect fill="url(#83fd4e5a-9d52-42fc-97b6-718e5d7ee527)" width="100%" height="100%" strokeWidth={0} />
</svg>
<div
aria-hidden="true"
className="absolute top-[calc(100%-13rem)] -left-56 hidden transform-gpu blur-3xl lg:top-[calc(50%-7rem)] lg:left-[max(-14rem,calc(100%-59rem))]"
>
<div
style={{
clipPath:
'polygon(74.1% 56.1%, 100% 38.6%, 97.5% 73.3%, 85.5% 100%, 80.7% 98.2%, 72.5% 67.7%, 60.2% 37.8%, 52.4% 32.2%, 47.5% 41.9%, 45.2% 65.8%, 27.5% 23.5%, 0.1% 35.4%, 17.9% 0.1%, 27.6% 23.5%, 76.1% 2.6%, 74.1% 56.1%)',
}}
className="aspect-1155/678 w-288.75 bg-linear-to-br from-[#80caff] to-[#4f46e5] opacity-10"
/>
</div>
</div>
<h2 className="text-4xl font-semibold tracking-tight text-pretty text-gray-900 sm:text-5xl">
Get in Touch
</h2>
<p className="mt-6 text-lg/8 text-gray-600">
At VEDA, we welcome your questions, ideas, and collaborations. Whether youre curious about our retreats, interested in hosting a private gathering, or exploring partnership opportunities, were here to connect. Reach out to us today and lets create transformative experiences along the timeless Nile.
</p>
<dl className="mt-10 space-y-4 text-base/7 text-gray-600">
<div className="flex gap-x-4">
<dt className="flex-none">
<span className="sr-only">Email</span>
<EnvelopeIcon aria-hidden="true" className="h-7 w-6 text-gray-400" />
</dt>
<dd>
<a href="mailto:info@veda-egypt.com" className="hover:text-gray-900">
info@veda-egypt.com
</a>
</dd>
</div>
<div className="flex gap-x-4">
<dt className="flex-none">
<span className="sr-only">Reservations</span>
<EnvelopeIcon aria-hidden="true" className="h-7 w-6 text-gray-400" />
</dt>
<dd>
<a href="mailto:reservation@veda-egypt.com" className="hover:text-gray-900">
reservation@veda-egypt.com
</a>
</dd>
</div>
</dl>
</div>
</div>
{/* Right container (image) */}
<div className="relative bg-gray-100 overflow-hidden px-6 lg:px-8">
<div className="h-full w-full overflow-hidden">
<Image
src="/images/contact/contact.png"
alt="VEDA contact"
width={900}
height={600}
className="h-full w-full object-cover"
/>
</div>
</div>
</div>
</div>
)
}

253
src/components/Events.jsx Normal file
View File

@@ -0,0 +1,253 @@
'use client'
import { Fragment, useState } from 'react'
import {
Dialog,
DialogBackdrop,
DialogPanel,
Disclosure,
DisclosureButton,
DisclosurePanel,
Popover,
PopoverButton,
PopoverGroup,
PopoverPanel,
Tab,
TabGroup,
TabList,
TabPanel,
TabPanels,
} from '@headlessui/react'
import {
Bars3Icon,
HeartIcon,
MagnifyingGlassIcon,
MinusIcon,
PlusIcon,
ShoppingBagIcon,
UserIcon,
XMarkIcon,
} from '@heroicons/react/24/outline'
import { StarIcon } from '@heroicons/react/20/solid'
const product = {
name: 'Events & Conferences',
images: [
{
id: 1,
name: 'Event Space 1',
src: '/images/events1.jpg',
alt: 'Professional event space with Nile backdrop',
},
{
id: 2,
name: 'Event Space 2',
src: '/images/events2.jpg',
alt: 'Conference venue with modern amenities',
},
{
id: 3,
name: 'Event Space 3',
src: '/images/events3.jpg',
alt: 'Meeting space on dahabiya',
},
{
id: 4,
name: 'Event Space 4',
src: '/images/events4.jpg',
alt: 'Outdoor event setting by the Nile',
},
],
colors: [
{ id: 'washed-black', name: 'Washed Black', classes: 'bg-gray-700 checked:outline-gray-700' },
{ id: 'white', name: 'White', classes: 'bg-white checked:outline-gray-400' },
{ id: 'washed-gray', name: 'Washed Gray', classes: 'bg-gray-500 checked:outline-gray-500' },
],
description: `
<p>VEDA's facilities are suited for hosting events, a unique venue that combines professional functionality with the tranquility of the Nile. An innovative setting that stands apart from traditional conference venues, promising an impactful event.</p>
`,
details: [
{
name: 'Unique Features',
items: [
'Four Air-Conditioned Meeting Spaces equipped with modern amenities to host up to 50/100 participants, perfect for workshops, seminars, and more.',
'From indoor workshops to outdoor receptions and gala dinners, the settings are as versatile as the events themselves.',
'Comprehensive Services: This includes catering, technical equipment, and event planning assistance to ensure everything runs smoothly.',
'Essential for modern retreats and conferences, we offer reliable internet and state-of-the-art meeting equipment.',
'Flexible Event Locations Across the Nile: Offering the freedom to choose unique meeting locations for an unparalleled experience.',
],
},
{
name: 'Benefits',
items: [
'Unique venue: The natural and historical backdrop of the Nile serves as a source of inspiration and tranquility.',
'Modern Conveniences: Internet, flexible meeting locations, and comprehensive support services facilitate a productive and engaging environment.',
],
},
],
}
const relatedProducts = [
{
id: 1,
name: 'Zip Tote Basket',
color: 'White and black',
href: '#',
imageSrc: 'https://tailwindcss.com/plus-assets/img/ecommerce-images/product-page-03-related-product-01.jpg',
imageAlt: 'Front of zip tote bag with white canvas, black canvas straps and handle, and black zipper pulls.',
price: '$140',
},
{
id: 2,
name: 'Zip High Wall Tote',
color: 'White and blue',
href: '#',
imageSrc: 'https://tailwindcss.com/plus-assets/img/ecommerce-images/product-page-03-related-product-02.jpg',
imageAlt: 'Front of zip tote bag with white canvas, blue canvas straps and handle, and front zipper pocket.',
price: '$150',
},
{
id: 3,
name: 'Halfsize Tote',
color: 'Clay',
href: '#',
imageSrc: 'https://tailwindcss.com/plus-assets/img/ecommerce-images/product-page-03-related-product-03.jpg',
imageAlt: 'Front of tote with monochrome natural canvas body, straps, roll top, and handles.',
price: '$210',
},
{
id: 4,
name: 'High Wall Tote',
color: 'Black and orange',
href: '#',
imageSrc: 'https://tailwindcss.com/plus-assets/img/ecommerce-images/product-page-03-related-product-04.jpg',
imageAlt: 'Front of zip tote bag with black canvas, black handles, and orange drawstring top.',
price: '$210',
},
]
function classNames(...classes) {
return classes.filter(Boolean).join(' ')
}
export function Events() {
const [open, setOpen] = useState(false)
return (
<div className="pb-24">
<main className="mx-auto max-w-7xl sm:px-6 sm:pt-16 lg:px-8">
<div className="mx-auto max-w-2xl lg:max-w-none">
{/* Product */}
<div className="lg:grid lg:grid-cols-2 lg:items-start lg:gap-x-8">
{/* Image gallery */}
<TabGroup className="flex flex-col-reverse">
{/* Image selector */}
<div className="mx-auto mt-6 hidden w-full max-w-2xl sm:block lg:max-w-none">
<TabList className="grid grid-cols-4 gap-6">
{product.images.map((image) => (
<Tab
key={image.id}
className="group relative flex h-24 cursor-pointer items-center justify-center rounded-md bg-white text-sm font-medium text-gray-900 uppercase hover:bg-gray-50 focus:ring-3 focus:ring-indigo-500/50 focus:ring-offset-4 focus:outline-hidden"
>
<span className="sr-only">{image.name}</span>
<span className="absolute inset-0 overflow-hidden rounded-md">
<img alt="" src={image.src} className="size-full object-cover" />
</span>
<span
aria-hidden="true"
className="pointer-events-none absolute inset-0 rounded-md ring-2 ring-transparent ring-offset-2 group-data-selected:ring-indigo-500"
/>
</Tab>
))}
</TabList>
</div>
<TabPanels>
{product.images.map((image) => (
<TabPanel key={image.id}>
<img alt={image.alt} src={image.src} className="aspect-square w-full object-cover sm:rounded-lg" />
</TabPanel>
))}
</TabPanels>
</TabGroup>
{/* Product info */}
<div className="mt-10 px-4 sm:mt-16 sm:px-0 lg:mt-0">
<h1 className="text-3xl font-bold tracking-tight text-gray-900">{product.name}</h1>
<div className="mt-3">
<h2 className="sr-only">Product information</h2>
</div>
{/* Reviews */}
<div className="mt-3">
<h3 className="sr-only">Reviews</h3>
</div>
<div className="mt-6">
<h3 className="sr-only">Description</h3>
<div
dangerouslySetInnerHTML={{ __html: product.description }}
className="space-y-6 text-base text-gray-700"
/>
</div>
<section aria-labelledby="details-heading" className="mt-12">
<h2 id="details-heading" className="sr-only">
Additional details
</h2>
<div className="divide-y divide-gray-200 border-t border-gray-200">
{product.details.map((detail) => (
<Disclosure key={detail.name} as="div">
<h3>
<DisclosureButton className="group relative flex w-full items-center justify-between py-6 text-left">
<span className="text-sm font-medium text-gray-900 group-data-open:text-indigo-600">
{detail.name}
</span>
<span className="ml-6 flex items-center">
<PlusIcon
aria-hidden="true"
className="block size-6 text-gray-400 group-hover:text-gray-500 group-data-open:hidden"
/>
<MinusIcon
aria-hidden="true"
className="hidden size-6 text-indigo-400 group-hover:text-indigo-500 group-data-open:block"
/>
</span>
</DisclosureButton>
</h3>
<DisclosurePanel className="pb-6">
<ul
role="list"
className="list-disc space-y-1 pl-5 text-sm/6 text-gray-700 marker:text-gray-300"
>
{detail.items.map((item) => (
<li key={item} className="pl-2">
{item}
</li>
))}
</ul>
</DisclosurePanel>
</Disclosure>
))}
</div>
</section>
</div>
</div>
</div>
</main>
</div>
)
}

View File

@@ -0,0 +1,87 @@
export function Experiences() {
return (
<div className="py-24">
<div className="mx-auto max-w-2xl px-6 lg:max-w-7xl lg:px-8">
<h2 className="text-base font-semibold leading-7 text-gold-600">PROGRAMS</h2>
<p className="mt-2 max-w-lg text-4xl font-semibold tracking-tight text-pretty text-gray-900 sm:text-5xl">
VEDA EXPERIENCES
</p>
<p className="mt-6 max-w-3xl text-lg leading-8 text-gray-600">
From intimate community gatherings to professional retreats, VEDA offers transformative experiences on the Nile. Each space is thoughtfully designed to foster connection, creativity, and personal growth in an inspiring riverside setting.
</p>
<div className="mt-10 grid grid-cols-1 gap-4 sm:mt-16 lg:grid-cols-8 lg:grid-rows-2">
<div className="flex p-px lg:col-span-5">
<div className="w-full overflow-hidden rounded-lg bg-white shadow-sm outline outline-black/5 max-lg:rounded-t-4xl lg:rounded-tl-4xl">
<div className="h-80 w-full overflow-hidden">
<img
alt=""
src="/images/community.jpg"
className="h-full w-full object-cover object-center"
/>
</div>
<div className="p-10">
<p className="mt-2 text-lg font-medium tracking-tight text-gray-900">Community building Space</p>
<p className="mt-2 max-w-xl text-sm/6 text-gray-600">
Community building holds significant value by fostering a sense of belonging, providing social and emotional support, and promoting collective action. It enhances social capital, encourages collaboration. Human to human connection become more and more important as new technological times require us to collaborate more on a peer to peer level and connect on different levels with each other.
</p>
</div>
</div>
</div>
<div className="flex p-px lg:col-span-3">
<div className="w-full overflow-hidden rounded-lg bg-white shadow-sm outline outline-black/5 lg:rounded-tr-4xl">
<div className="h-80 w-full overflow-hidden">
<img
alt=""
src="/images/private.jpg"
className="h-full w-full object-cover object-center"
/>
</div>
<div className="p-10">
<p className="mt-2 text-lg font-medium tracking-tight text-gray-900">Private Retreats</p>
<p className="mt-2 max-w-lg text-sm/6 text-gray-600">
Designed for companies, private groups or families, veda is happy to organise your exclusive retreat experience.
These retreats offer the flexibility to design the itinerary and activities based on the groups specific interests and needs.
</p>
</div>
</div>
</div>
<div className="flex p-px lg:col-span-3">
<div className="w-full overflow-hidden rounded-lg bg-white shadow-sm outline outline-black/5 lg:rounded-bl-4xl">
<div className="h-80 w-full overflow-hidden">
<img
alt=""
src="/images/events.jpg"
className="h-full w-full object-cover object-center"
/>
</div>
<div className="p-10">
<p className="mt-2 text-lg font-medium tracking-tight text-gray-900">Events & Conferences</p>
<p className="mt-2 max-w-lg text-sm/6 text-gray-600">
VEDAs facilities are suited for hosting events, a unique venue that combines professional functionality with the tranquility of the Nile.
An innovative setting that stands apart from traditional conference venues, promising a impactful event.
</p>
</div>
</div>
</div>
<div className="flex p-px lg:col-span-5">
<div className="w-full overflow-hidden rounded-lg bg-white shadow-sm outline outline-black/5 max-lg:rounded-b-4xl lg:rounded-br-4xl">
<div className="h-80 w-full overflow-hidden">
<img
alt=""
src="/images/nomads.jpg"
className="h-full w-full object-cover object-center"
/>
</div>
<div className="p-10">
<p className="mt-2 text-lg font-medium tracking-tight text-gray-900">Digital Nomad Hub</p>
<p className="mt-2 max-w-lg text-sm/6 text-gray-600">
A haven for remote workers seeking inspiration, focus, and balance. Our spaces combine reliable connectivity, comfortable work areas, and serene surroundings creating the perfect environment to blend productivity with rejuvenation.
</p>
</div>
</div>
</div>
</div>
</div>
</div>
)
}

View File

@@ -1,47 +1,14 @@
import Link from 'next/link'
import { Container } from '@/components/Container'
import { Logo } from '@/components/Logo'
import { NavLink } from '@/components/NavLink'
export function Footer() {
return (
<footer className="bg-creme-900">
<Container>
<div className="py-16">
<Logo className="mx-auto h-10 w-auto" />
<nav className="mt-10 text-sm" aria-label="quick links">
<div className="-my-1 flex justify-center gap-x-6">
<NavLink href="#features">Features</NavLink>
<NavLink href="#testimonials">Testimonials</NavLink>
<NavLink href="#pricing">Pricing</NavLink>
</div>
</nav>
</div>
<div className="flex flex-col items-center border-t border-slate-400/10 py-10 sm:flex-row-reverse sm:justify-between">
<div className="flex gap-x-6">
<Link href="#" className="group" aria-label="VEDA on X">
<svg
className="h-6 w-6 fill-slate-500 group-hover:fill-slate-700"
aria-hidden="true"
viewBox="0 0 24 24"
>
<path d="M13.3174 10.7749L19.1457 4H17.7646L12.7039 9.88256L8.66193 4H4L10.1122 12.8955L4 20H5.38119L10.7254 13.7878L14.994 20H19.656L13.3171 10.7749H13.3174ZM11.4257 12.9738L10.8064 12.0881L5.87886 5.03974H8.00029L11.9769 10.728L12.5962 11.6137L17.7652 19.0075H15.6438L11.4257 12.9742V12.9738Z" />
</svg>
</Link>
<Link href="#" className="group" aria-label="VEDA on GitHub">
<svg
className="h-6 w-6 fill-slate-500 group-hover:fill-slate-700"
aria-hidden="true"
viewBox="0 0 24 24"
>
<path d="M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0 1 12 6.844a9.59 9.59 0 0 1 2.504.337c1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.02 10.02 0 0 0 22 12.017C22 6.484 17.522 2 12 2Z" />
</svg>
</Link>
</div>
<p className="mt-6 text-sm text-slate-500 sm:mt-0">
Copyright &copy; {new Date().getFullYear()} VEDA. All rights
reserved.
<div className="flex items-center justify-between py-16">
<Logo className="h-10 w-auto" />
<p className="text-sm text-slate-500">
Copyright &copy; {new Date().getFullYear()} VEDA. All rights reserved.
</p>
</div>
</Container>

View File

@@ -60,7 +60,7 @@ function MobileNavigation() {
</PopoverButton>
<PopoverBackdrop
transition
className="fixed inset-0 bg-slate-300/50 duration-150 data-[closed]:opacity-0 data-[enter]:ease-out data-[leave]:ease-in"
className="fixed inset-0 bg-slate-50/50 duration-150 data-[closed]:opacity-0 data-[enter]:ease-out data-[leave]:ease-in"
/>
<PopoverPanel
transition
@@ -68,12 +68,11 @@ function MobileNavigation() {
>
<MobileNavLink href="/">HOME</MobileNavLink>
<MobileNavLink href="/story">STORY</MobileNavLink>
<MobileNavLink href="/dahabiyas">DAHABIYAS</MobileNavLink>
<MobileNavLink href="/experiences">EXPERIENCES</MobileNavLink>
<NavLink href="/itinerary">ITINERARY</NavLink>
<MobileNavLink href="/gallery">GALLERY</MobileNavLink>
<MobileNavLink href="/dahabiyas">DAHABIYAS</MobileNavLink>
<MobileNavLink href="/itinerary">ITINERARY</MobileNavLink>
<hr className="m-2 border-slate-300/40" />
<MobileNavLink href="/login">BOOK NOW</MobileNavLink>
<MobileNavLink href="/contact">BOOK NOW</MobileNavLink>
</PopoverPanel>
</Popover>
)
@@ -81,24 +80,23 @@ function MobileNavigation() {
export function Header() {
return (
<header className="py-10">
<header className="fixed top-0 left-0 right-0 z-50 bg-black/20 backdrop-blur-sm py-4">
<Container>
<nav className="relative z-50 flex justify-between">
<div className="flex items-center md:gap-x-12">
<Link href="#" aria-label="Home">
<Logo className="h-10 w-auto" />
<Link href="/" aria-label="Home" className="overflow-visible">
<Logo className="h-8 w-auto max-w-none sm:h-9 md:h-10 lg:h-11 xl:h-12" />
</Link>
<div className="hidden md:flex md:gap-x-6">
<NavLink href="/">HOME</NavLink>
<NavLink href="/story">STORY</NavLink>
<NavLink href="/dahabiyas">DAHABIYAS</NavLink>
<NavLink href="/experiences">EXPERIENCES</NavLink>
<NavLink href="/dahabiyas">DAHABIYAS</NavLink>
<NavLink href="/itinerary">ITINERARY</NavLink>
<NavLink href="#pricing">GALLERY</NavLink>
</div>
</div>
<div className="flex items-center gap-x-5 md:gap-x-8">
<Button href="/register" color="blue">
<Button href="/contact" color="blue">
<span className="font-semibold tracking-wide">
Book Now
</span>

View File

@@ -43,7 +43,7 @@ export function Hero() {
<span className="relative">REIMAGINED</span>
</span>{' '}
</h1>
<p className="relative z-10 mx-auto mt-6 max-w-2xl lg:text-xl text-sm tracking-wide font-normal text-slate-300">
<p className="relative z-10 mx-auto mt-6 max-w-2xl lg:text-xl text-sm tracking-wide font-normal text-slate-100">
Discover ancient Egypt and feel its special energies across the enchanting Nile river with our sophisticated cruises, boat rentals, healing packages, corporate retreats, private holidays, & more.
</p>
<div className="relative z-10 mt-10 mb-96 lg:mb-40 lg:py-2 xs:mb-20 flex justify-center gap-x-6 relative z-10">
@@ -59,8 +59,8 @@ export function Hero() {
{/* SECOND SECTION */}
<div className="relative z-10 lg:pt-10">
<p className="font-display text-large lg:text-2xl font-semibold italic text-slate-900">
<div className="relative z-10 pt-10">
<p className="mt-10 font-display text-large lg:text-2xl font-semibold italic text-slate-900">
As seen and featured on
</p>
<ul

File diff suppressed because one or more lines are too long

View File

@@ -4,7 +4,7 @@ export function NavLink({ href, children }) {
return (
<Link
href={href}
className="inline-block rounded-lg px-2 py-1 text-sm text-gray-400 hover:bg-gold-600 hover:text-slate-900"
className="inline-block rounded-lg px-2 py-1 text-sm text-white hover:bg-gold-600 hover:text-slate-900"
>
{children}
</Link>

206
src/components/Nomads.jsx Normal file
View File

@@ -0,0 +1,206 @@
'use client'
import { Fragment, useState } from 'react'
import {
Dialog,
DialogBackdrop,
DialogPanel,
Disclosure,
DisclosureButton,
DisclosurePanel,
Popover,
PopoverButton,
PopoverGroup,
PopoverPanel,
Tab,
TabGroup,
TabList,
TabPanel,
TabPanels,
} from '@headlessui/react'
import {
Bars3Icon,
HeartIcon,
MagnifyingGlassIcon,
MinusIcon,
PlusIcon,
ShoppingBagIcon,
UserIcon,
XMarkIcon,
} from '@heroicons/react/24/outline'
import { StarIcon } from '@heroicons/react/20/solid'
const product = {
name: 'Digital Nomads Hub',
images: [
{
id: 1,
name: 'Digital Nomad Space 1',
src: '/images/nomads1.jpg',
alt: 'Digital nomad workspace with Nile views',
},
{
id: 2,
name: 'Digital Nomad Space 2',
src: '/images/nomads2.jpg',
alt: 'Co-working area on dahabiya',
},
{
id: 3,
name: 'Digital Nomad Space 3',
src: '/images/nomads3.jpg',
alt: 'Remote work setup with river backdrop',
},
{
id: 4,
name: 'Digital Nomad Space 4',
src: '/images/nomads4.jpg',
alt: 'Inspiring workspace for digital nomads',
},
],
colors: [
{ id: 'washed-black', name: 'Washed Black', classes: 'bg-gray-700 checked:outline-gray-700' },
{ id: 'white', name: 'White', classes: 'bg-white checked:outline-gray-400' },
{ id: 'washed-gray', name: 'Washed Gray', classes: 'bg-gray-500 checked:outline-gray-500' },
],
description: `
<p>A haven for remote workers seeking inspiration, focus, and balance. Our spaces combine reliable connectivity, comfortable work areas, and serene surroundings — creating the perfect environment to blend productivity with rejuvenation.</p>
`,
details: [
{
name: 'Unique Features',
items: [
'Dedicated co-working areas onboard and ashore, equipped with reliable high-speed internet and comfortable workstations.',
'Flexible daily schedules that allow guests to balance focused work sessions with leisure, cultural exploration, and wellness activities.',
'Access to wellness amenities such as yoga, meditation, and massage, ensuring physical and mental well-being while working remotely.',
'Opportunities to network and collaborate with other professionals, fostering creativity and idea exchange in an inspiring setting.',
],
},
{
name: 'Benefits',
items: [
'The serene backdrop of the Nile enhances focus, productivity, and creative thinking.',
'A harmonious lifestyle that blends professional output with personal rejuvenation and cultural immersion.',
'Connections with like-minded nomads from around the world, building both professional and personal networks.',
'The freedom to work from anywhere while enjoying the comfort, beauty, and inspiration of an extraordinary travel experience.',
],
},
],
}
function classNames(...classes) {
return classes.filter(Boolean).join(' ')
}
export function Nomads() {
const [open, setOpen] = useState(false)
return (
<div className="pb-24">
<main className="mx-auto max-w-7xl sm:px-6 sm:pt-16 lg:px-8">
<div className="mx-auto max-w-2xl lg:max-w-none">
{/* Product */}
<div className="lg:grid lg:grid-cols-2 lg:items-start lg:gap-x-8">
{/* Product info */}
<div className="mt-10 px-4 sm:mt-16 sm:px-0 lg:mt-0">
<h1 className="text-3xl font-bold tracking-tight text-gray-900">{product.name}</h1>
<div className="mt-3">
<h2 className="sr-only">Product information</h2>
</div>
{/* Reviews */}
<div className="mt-3">
<h3 className="sr-only">Reviews</h3>
</div>
<div className="mt-6">
<h3 className="sr-only">Description</h3>
<div
dangerouslySetInnerHTML={{ __html: product.description }}
className="space-y-6 text-base text-gray-700"
/>
</div>
<section aria-labelledby="details-heading" className="mt-12">
<h2 id="details-heading" className="sr-only">
Additional details
</h2>
<div className="divide-y divide-gray-200 border-t border-gray-200">
{product.details.map((detail) => (
<Disclosure key={detail.name} as="div">
<h3>
<DisclosureButton className="group relative flex w-full items-center justify-between py-6 text-left">
<span className="text-sm font-medium text-gray-900 group-data-open:text-indigo-600">
{detail.name}
</span>
<span className="ml-6 flex items-center">
<PlusIcon
aria-hidden="true"
className="block size-6 text-gray-400 group-hover:text-gray-500 group-data-open:hidden"
/>
<MinusIcon
aria-hidden="true"
className="hidden size-6 text-indigo-400 group-hover:text-indigo-500 group-data-open:block"
/>
</span>
</DisclosureButton>
</h3>
<DisclosurePanel className="pb-6">
<ul
role="list"
className="list-disc space-y-1 pl-5 text-sm/6 text-gray-700 marker:text-gray-300"
>
{detail.items.map((item) => (
<li key={item} className="pl-2">
{item}
</li>
))}
</ul>
</DisclosurePanel>
</Disclosure>
))}
</div>
</section>
</div>
{/* Image gallery */}
<TabGroup className="flex flex-col-reverse">
{/* Image selector */}
<div className="mx-auto mt-6 hidden w-full max-w-2xl sm:block lg:max-w-none">
<TabList className="grid grid-cols-4 gap-6">
{product.images.map((image) => (
<Tab
key={image.id}
className="group relative flex h-24 cursor-pointer items-center justify-center rounded-md bg-white text-sm font-medium text-gray-900 uppercase hover:bg-gray-50 focus:ring-3 focus:ring-indigo-500/50 focus:ring-offset-4 focus:outline-hidden"
>
<span className="sr-only">{image.name}</span>
<span className="absolute inset-0 overflow-hidden rounded-md">
<img alt="" src={image.src} className="size-full object-cover" />
</span>
<span
aria-hidden="true"
className="pointer-events-none absolute inset-0 rounded-md ring-2 ring-transparent ring-offset-2 group-data-selected:ring-indigo-500"
/>
</Tab>
))}
</TabList>
</div>
<TabPanels>
{product.images.map((image) => (
<TabPanel key={image.id}>
<img alt={image.alt} src={image.src} className="aspect-square w-full object-cover sm:rounded-lg" />
</TabPanel>
))}
</TabPanels>
</TabGroup>
</div>
</div>
</main>
</div>
)
}

204
src/components/Retreats.jsx Normal file
View File

@@ -0,0 +1,204 @@
'use client'
import { Fragment, useState } from 'react'
import {
Dialog,
DialogBackdrop,
DialogPanel,
Disclosure,
DisclosureButton,
DisclosurePanel,
Popover,
PopoverButton,
PopoverGroup,
PopoverPanel,
Tab,
TabGroup,
TabList,
TabPanel,
TabPanels,
} from '@headlessui/react'
import {
Bars3Icon,
HeartIcon,
MagnifyingGlassIcon,
MinusIcon,
PlusIcon,
ShoppingBagIcon,
UserIcon,
XMarkIcon,
} from '@heroicons/react/24/outline'
import { StarIcon } from '@heroicons/react/20/solid'
const product = {
name: 'Private Retreats',
images: [
{
id: 1,
name: 'Private Retreat Space 1',
src: '/images/private.jpg',
alt: 'Private retreat space with dahabiya setting',
},
{
id: 2,
name: 'Private Retreat Space 2',
src: '/images/veda2.jpg',
alt: 'Elegant dahabiya for private groups',
},
{
id: 3,
name: 'Private Retreat Space 3',
src: '/images/veda3.jpg',
alt: 'Private retreat on the Nile',
},
{
id: 4,
name: 'Private Retreat Space 4',
src: '/images/veda4.jpg',
alt: 'Exclusive dahabiya experience',
},
],
colors: [
{ id: 'washed-black', name: 'Washed Black', classes: 'bg-gray-700 checked:outline-gray-700' },
{ id: 'white', name: 'White', classes: 'bg-white checked:outline-gray-400' },
{ id: 'washed-gray', name: 'Washed Gray', classes: 'bg-gray-500 checked:outline-gray-500' },
],
description: `
<p>Groups enjoy privacy and a highly personalized experience aboard our elegant and homy dahabiya, ensuring a vibrant, exclusive and secluded setting.</p>
`,
details: [
{
name: 'Features',
items: [
'Groups enjoy privacy and a highly personalized experience aboard our elegant and homy dahabiya, ensuring a vibrant, exclusive and secluded setting.',
'Options range from corporate wellness programs and team-building exercises to energy practices, yoga sessions, and group\'s own activities.',
'Flexible itineraries: Groups can choose their journey stops, allowing for a tailored experience that can include private tours of historical landmarks, meditation in ancient temples, or leisurely sails to less-known Nile locations.',
],
},
{
name: 'Benefits',
items: [
'The tranquil and culturally rich environment of the Nile encourages reflection, ideation, and personal growth.',
'The fusion of personalized wellness, cultural immersion, and luxury in a private setting ensures a unique journey that aligns with the group\'s interests.',
'Retreats are adaptable in length and activities, allowing for a program that perfectly fits the group\'s schedule and desired outcomes, whether for a few days or an extended period.',
],
},
],
}
function classNames(...classes) {
return classes.filter(Boolean).join(' ')
}
export function Retreats() {
const [open, setOpen] = useState(false)
return (
<div className="pb-24">
<main className="mx-auto max-w-7xl sm:px-6 sm:pt-16 lg:px-8">
<div className="mx-auto max-w-2xl lg:max-w-none">
{/* Product */}
<div className="lg:grid lg:grid-cols-2 lg:items-start lg:gap-x-8">
{/* Product info */}
<div className="mt-10 px-4 sm:mt-16 sm:px-0 lg:mt-0">
<h1 className="text-3xl font-bold tracking-tight text-gray-900">{product.name}</h1>
<div className="mt-3">
<h2 className="sr-only">Product information</h2>
</div>
{/* Reviews */}
<div className="mt-3">
<h3 className="sr-only">Reviews</h3>
</div>
<div className="mt-6">
<h3 className="sr-only">Description</h3>
<div
dangerouslySetInnerHTML={{ __html: product.description }}
className="space-y-6 text-base text-gray-700"
/>
</div>
<section aria-labelledby="details-heading" className="mt-12">
<h2 id="details-heading" className="sr-only">
Additional details
</h2>
<div className="divide-y divide-gray-200 border-t border-gray-200">
{product.details.map((detail) => (
<Disclosure key={detail.name} as="div">
<h3>
<DisclosureButton className="group relative flex w-full items-center justify-between py-6 text-left">
<span className="text-sm font-medium text-gray-900 group-data-open:text-indigo-600">
{detail.name}
</span>
<span className="ml-6 flex items-center">
<PlusIcon
aria-hidden="true"
className="block size-6 text-gray-400 group-hover:text-gray-500 group-data-open:hidden"
/>
<MinusIcon
aria-hidden="true"
className="hidden size-6 text-indigo-400 group-hover:text-indigo-500 group-data-open:block"
/>
</span>
</DisclosureButton>
</h3>
<DisclosurePanel className="pb-6">
<ul
role="list"
className="list-disc space-y-1 pl-5 text-sm/6 text-gray-700 marker:text-gray-300"
>
{detail.items.map((item) => (
<li key={item} className="pl-2">
{item}
</li>
))}
</ul>
</DisclosurePanel>
</Disclosure>
))}
</div>
</section>
</div>
{/* Image gallery */}
<TabGroup className="flex flex-col-reverse">
{/* Image selector */}
<div className="mx-auto mt-6 hidden w-full max-w-2xl sm:block lg:max-w-none">
<TabList className="grid grid-cols-4 gap-6">
{product.images.map((image) => (
<Tab
key={image.id}
className="group relative flex h-24 cursor-pointer items-center justify-center rounded-md bg-white text-sm font-medium text-gray-900 uppercase hover:bg-gray-50 focus:ring-3 focus:ring-indigo-500/50 focus:ring-offset-4 focus:outline-hidden"
>
<span className="sr-only">{image.name}</span>
<span className="absolute inset-0 overflow-hidden rounded-md">
<img alt="" src={image.src} className="size-full object-cover" />
</span>
<span
aria-hidden="true"
className="pointer-events-none absolute inset-0 rounded-md ring-2 ring-transparent ring-offset-2 group-data-selected:ring-indigo-500"
/>
</Tab>
))}
</TabList>
</div>
<TabPanels>
{product.images.map((image) => (
<TabPanel key={image.id}>
<img alt={image.alt} src={image.src} className="aspect-square w-full object-cover sm:rounded-lg" />
</TabPanel>
))}
</TabPanels>
</TabGroup>
</div>
</div>
</main>
</div>
)
}

14
src/components/link.jsx Normal file
View File

@@ -0,0 +1,14 @@
import NextLink from 'next/link'
import { clsx } from 'clsx'
export function Link({ href, className, children, ...props }) {
return (
<NextLink
href={href}
className={clsx(className)}
{...props}
>
{children}
</NextLink>
)
}

29
src/components/text.jsx Normal file
View File

@@ -0,0 +1,29 @@
import { clsx } from 'clsx'
export function Heading({ as: Component = 'h1', className, children, ...props }) {
return (
<Component
className={clsx(
'text-4xl font-bold tracking-tight text-gray-900 sm:text-5xl',
className
)}
{...props}
>
{children}
</Component>
)
}
export function Subheading({ className, children, ...props }) {
return (
<h3
className={clsx(
'text-lg font-medium text-gray-900',
className
)}
{...props}
>
{children}
</h3>
)
}