Next.js 14 website with standalone output configured for Docker deployment. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
219 lines
9.8 KiB
TypeScript
219 lines
9.8 KiB
TypeScript
"use client";
|
|
|
|
import { motion, useInView } from "framer-motion";
|
|
import { useRef } from "react";
|
|
import Image from "next/image";
|
|
import Link from "next/link";
|
|
import { experiences } from "@/lib/constants";
|
|
import { SectionHeader } from "@/components/shared/SectionHeader";
|
|
import { Button } from "@/components/shared/Button";
|
|
|
|
function ExperienceCard({
|
|
experience,
|
|
index,
|
|
featured = false,
|
|
}: {
|
|
experience: (typeof experiences)[0];
|
|
index: number;
|
|
featured?: boolean;
|
|
}) {
|
|
const ref = useRef(null);
|
|
const isInView = useInView(ref, { once: true, margin: "-50px" });
|
|
|
|
if (featured) {
|
|
return (
|
|
<motion.div
|
|
ref={ref}
|
|
initial={{ opacity: 0, y: 30 }}
|
|
animate={isInView ? { opacity: 1, y: 0 } : {}}
|
|
transition={{ delay: 0.1, duration: 0.6 }}
|
|
className="col-span-full"
|
|
>
|
|
<Link href={`/experiences/${experience.slug}`} className="group block">
|
|
<div className="relative bg-white rounded-3xl overflow-hidden shadow-lg hover:shadow-xl transition-shadow">
|
|
<div className="grid grid-cols-1 lg:grid-cols-2">
|
|
<div className="relative aspect-[4/3] lg:aspect-auto">
|
|
<Image
|
|
src={experience.image}
|
|
alt={`${experience.name} — ${experience.hook}`}
|
|
fill
|
|
className="object-cover group-hover:scale-105 transition-transform duration-700"
|
|
sizes="(max-width: 1024px) 100vw, 50vw"
|
|
/>
|
|
<div
|
|
className="absolute -top-1 -left-1 bg-sun-yolk text-deep-nazar px-5 py-2 rounded-br-2xl rounded-tl-3xl text-xs font-bold uppercase tracking-wide"
|
|
style={{ boxShadow: "0 4px 14px rgba(0,0,0,0.15), 0 1px 4px rgba(0,0,0,0.1)" }}
|
|
>
|
|
Popular — Sells out 3 days in advance
|
|
</div>
|
|
</div>
|
|
<div className="p-8 lg:p-12 flex flex-col justify-center">
|
|
<p className="text-bosphorus font-semibold text-sm tracking-widest uppercase mb-2">
|
|
{experience.tagline}
|
|
</p>
|
|
<h3 className="font-display text-3xl md:text-4xl font-bold text-deep-nazar mb-4">
|
|
{experience.name}
|
|
</h3>
|
|
<p className="text-deep-nazar/70 text-lg leading-relaxed mb-6">
|
|
{experience.hook}
|
|
</p>
|
|
|
|
<div className="flex flex-wrap gap-2.5 mb-6">
|
|
{experience.highlights.map((h, i) => (
|
|
<span
|
|
key={i}
|
|
className="inline-flex items-center gap-1.5 bg-warm-sand rounded-full px-3 py-1.5 text-sm text-deep-nazar"
|
|
>
|
|
<span>{h.icon}</span>
|
|
{h.text}
|
|
</span>
|
|
))}
|
|
</div>
|
|
|
|
<div className="flex items-center justify-between pt-6 border-t border-deep-nazar/10">
|
|
<div className="flex flex-col">
|
|
<div className="flex items-center gap-4 text-sm text-deep-nazar/50 mb-1">
|
|
<span className="inline-flex items-center gap-1">
|
|
<svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" /></svg>
|
|
{experience.duration}
|
|
</span>
|
|
<span className="inline-flex items-center gap-1">
|
|
<svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0z" /></svg>
|
|
{experience.groupSize}
|
|
</span>
|
|
</div>
|
|
<div>
|
|
<span className="text-sm text-deep-nazar/60">From</span>
|
|
<span className="text-3xl font-bold text-deep-nazar ml-2">
|
|
€{experience.price}
|
|
</span>
|
|
<span className="text-deep-nazar/60">/person</span>
|
|
</div>
|
|
</div>
|
|
<span className="px-6 py-3 bg-coral-spritz text-white font-semibold rounded-full shadow-lg shadow-coral-spritz/25 group-hover:shadow-xl transition-shadow">
|
|
Book Now
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</Link>
|
|
</motion.div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<motion.div
|
|
ref={ref}
|
|
initial={{ opacity: 0, y: 30 }}
|
|
animate={isInView ? { opacity: 1, y: 0 } : {}}
|
|
transition={{ delay: index * 0.1, duration: 0.6 }}
|
|
>
|
|
<Link href={`/experiences/${experience.slug}`} className="group block h-full">
|
|
<div className="bg-white rounded-3xl overflow-hidden shadow-md hover:shadow-lg transition-all h-full flex flex-col">
|
|
<div className="relative aspect-[4/3] overflow-hidden">
|
|
<Image
|
|
src={experience.image}
|
|
alt={`${experience.name} — ${experience.hook}`}
|
|
fill
|
|
className="object-cover group-hover:scale-105 transition-transform duration-700"
|
|
sizes="(max-width: 768px) 100vw, (max-width: 1024px) 50vw, 33vw"
|
|
/>
|
|
</div>
|
|
<div className="p-6 flex flex-col flex-1">
|
|
<p className="text-bosphorus font-semibold text-xs tracking-widest uppercase mb-1">
|
|
{experience.tagline}
|
|
</p>
|
|
<h3 className="font-display text-xl font-bold text-deep-nazar mb-2">
|
|
{experience.name}
|
|
</h3>
|
|
<p className="text-deep-nazar/70 text-sm leading-relaxed mb-4">
|
|
{experience.hook}
|
|
</p>
|
|
|
|
<div className="flex flex-wrap gap-2 mb-4">
|
|
{experience.highlights.slice(0, 3).map((h, i) => (
|
|
<span
|
|
key={i}
|
|
className="inline-flex items-center gap-1 bg-warm-sand rounded-full px-2.5 py-1 text-xs text-deep-nazar"
|
|
>
|
|
<span>{h.icon}</span>
|
|
{h.text}
|
|
</span>
|
|
))}
|
|
</div>
|
|
|
|
<div className="flex items-center gap-4 mb-4 text-xs text-deep-nazar/50">
|
|
<span className="inline-flex items-center gap-1">
|
|
<svg className="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" /></svg>
|
|
{experience.duration}
|
|
</span>
|
|
<span className="inline-flex items-center gap-1">
|
|
<svg className="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0z" /></svg>
|
|
{experience.groupSize}
|
|
</span>
|
|
</div>
|
|
|
|
<div className="flex items-center justify-between pt-4 border-t border-deep-nazar/10 mt-auto">
|
|
<div>
|
|
<span className="text-xs text-deep-nazar/60">From </span>
|
|
<span className="text-xl font-bold text-deep-nazar">
|
|
€{experience.price}
|
|
</span>
|
|
</div>
|
|
<span className="px-4 py-2 border-2 border-coral-spritz text-coral-spritz font-semibold text-sm rounded-full group-hover:bg-coral-spritz group-hover:text-white transition-colors">
|
|
Book Now
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</Link>
|
|
</motion.div>
|
|
);
|
|
}
|
|
|
|
export function ExperienceGrid() {
|
|
const featured = experiences.find((e) => e.featured);
|
|
const others = experiences.filter((e) => !e.featured);
|
|
|
|
return (
|
|
<section id="experiences" className="py-20 md:py-32 section-padding">
|
|
<div className="max-w-7xl mx-auto">
|
|
<SectionHeader
|
|
eyebrow="Experiences"
|
|
title="Choose Your Edit"
|
|
subtitle="Every experience is handcrafted, small-group, and designed around the places we'd take our own friends."
|
|
/>
|
|
|
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
|
|
{featured && <ExperienceCard experience={featured} index={0} featured />}
|
|
{others.map((exp, i) => (
|
|
<ExperienceCard key={exp.slug} experience={exp} index={i + 1} />
|
|
))}
|
|
</div>
|
|
|
|
<motion.div
|
|
className="mt-16 bg-gradient-to-r from-deep-nazar to-deep-nazar/90 rounded-3xl p-8 md:p-12 text-white text-center"
|
|
initial={{ opacity: 0, y: 20 }}
|
|
whileInView={{ opacity: 1, y: 0 }}
|
|
viewport={{ once: true }}
|
|
>
|
|
<h3 className="font-display text-2xl md:text-3xl font-bold mb-3">
|
|
Private Editions
|
|
</h3>
|
|
<p className="text-white/80 text-lg mb-6 max-w-2xl mx-auto">
|
|
Want The Anatolian Edit all to yourself? Every experience is
|
|
available as a private edition for couples, families, or groups of up
|
|
to 12.
|
|
</p>
|
|
<div className="flex flex-col sm:flex-row items-center justify-center gap-4">
|
|
<Button href="/contact" variant="coral" size="lg">
|
|
Inquire About Private Experiences
|
|
</Button>
|
|
<span className="text-white/60 text-sm">Starting from €250</span>
|
|
</div>
|
|
</motion.div>
|
|
</div>
|
|
</section>
|
|
);
|
|
}
|