322 lines
12 KiB
TypeScript
322 lines
12 KiB
TypeScript
"use client";
|
|
|
|
import { useEffect, useState, useRef } from "react";
|
|
import { motion, AnimatePresence } from "framer-motion";
|
|
import { useRouter, usePathname } from "next/navigation";
|
|
import Image from "next/image";
|
|
import { MenuCategory, MenuItem as MenuItemType } from "@/data/menu";
|
|
import { CategoryNav } from "@/components/CategoryNav";
|
|
import { MenuItem } from "@/components/MenuItem";
|
|
import { getDictionary } from "@/lib/dictionaries";
|
|
|
|
export const CATEGORY_ICONS: Record<string, string> = {
|
|
"kahvaltiliklar": "🌅",
|
|
"kaseler": "🥗",
|
|
"burger-ve-sandvic": "🍔",
|
|
"pizzalar": "🍕",
|
|
"makarnalar": "🍝",
|
|
"baslangiclar": "🧆",
|
|
"salatalar": "🥬",
|
|
"ana-yemekler": "🍽️",
|
|
"tatlilar": "🍮",
|
|
"i̇mza-kokteyller": "🍹",
|
|
"klasik-kokteyller": "🍸",
|
|
"biralar": "🍺",
|
|
"shotlar": "🥃",
|
|
"sise-ve-kadeh-alkollu": "🥂",
|
|
"saraplar": "🍷",
|
|
"cerezler": "🥜",
|
|
"alkolsuz-i̇cecekler": "🧃",
|
|
"sicak-kahveler": "☕",
|
|
"soguk-kahveler": "🧊",
|
|
"ekstralar": "✨",
|
|
};
|
|
|
|
type SiteSettings = {
|
|
logoUrl: string
|
|
location: string
|
|
restaurantName: string
|
|
}
|
|
|
|
export default function MenuClient({ initialCategories, siteSettings, lang = "tr" }: { initialCategories: MenuCategory[], siteSettings: SiteSettings, lang?: string }) {
|
|
const [activeCategoryId, setActiveCategoryId] = useState<string>(
|
|
initialCategories[0]?.id || ""
|
|
);
|
|
const [selectedItem, setSelectedItem] = useState<MenuItemType | null>(null);
|
|
const sectionRefs = useRef<(HTMLElement | null)[]>([]);
|
|
|
|
const router = useRouter();
|
|
const pathname = usePathname();
|
|
const t = getDictionary(lang);
|
|
|
|
const switchLanguage = (newLang: string) => {
|
|
if (newLang === lang) return;
|
|
// Replace the current locale in the pathname
|
|
const newPath = pathname.replace(`/${lang}`, `/${newLang}`);
|
|
router.push(newPath || `/${newLang}`);
|
|
};
|
|
|
|
useEffect(() => {
|
|
const observer = new IntersectionObserver(
|
|
(entries) => {
|
|
const hit = entries.find((e) => e.isIntersecting);
|
|
if (hit) setActiveCategoryId(hit.target.id);
|
|
},
|
|
{ root: null, rootMargin: "-100px 0px -62% 0px", threshold: 0 }
|
|
);
|
|
|
|
sectionRefs.current.forEach((ref) => ref && observer.observe(ref));
|
|
return () => observer.disconnect();
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
if (selectedItem) {
|
|
document.body.style.overflow = "hidden";
|
|
} else {
|
|
document.body.style.overflow = "unset";
|
|
}
|
|
return () => {
|
|
document.body.style.overflow = "unset";
|
|
};
|
|
}, [selectedItem]);
|
|
|
|
return (
|
|
<div className="min-h-screen" style={{ background: "var(--cream)" }}>
|
|
|
|
{/* ── Hero ─────────────────────────────────────── */}
|
|
<header className="relative overflow-hidden bg-stone-900">
|
|
{/* Background Image */}
|
|
<div
|
|
className="absolute inset-0 z-0 opacity-80"
|
|
style={{
|
|
backgroundImage: "url('/header.jpg')",
|
|
backgroundSize: "cover",
|
|
backgroundPosition: "center"
|
|
}}
|
|
/>
|
|
|
|
{/* Language Switcher */}
|
|
<div className="absolute top-4 right-4 z-20 flex gap-2">
|
|
<button
|
|
onClick={() => switchLanguage('tr')}
|
|
className={`px-3 py-1.5 rounded-full text-[11px] font-sans tracking-widest uppercase transition-colors ${lang === 'tr' ? 'bg-white text-stone-900 font-bold' : 'bg-black/40 text-white/90 backdrop-blur-sm border border-white/20'}`}
|
|
>
|
|
TR
|
|
</button>
|
|
<button
|
|
onClick={() => switchLanguage('en')}
|
|
className={`px-3 py-1.5 rounded-full text-[11px] font-sans tracking-widest uppercase transition-colors ${lang === 'en' ? 'bg-white text-stone-900 font-bold' : 'bg-black/40 text-white/90 backdrop-blur-sm border border-white/20'}`}
|
|
>
|
|
EN
|
|
</button>
|
|
</div>
|
|
|
|
{/* Dark overlay to make text readable */}
|
|
<div className="absolute inset-0 z-0 bg-gradient-to-b from-black/60 via-black/40 to-black/80" />
|
|
|
|
{/* hero content */}
|
|
<div className="relative z-10 flex flex-col items-center text-center px-6 pt-16 pb-20">
|
|
<motion.div
|
|
initial={{ opacity: 0, y: 24 }}
|
|
animate={{ opacity: 1, y: 0 }}
|
|
transition={{ duration: 0.9, ease: [0.16, 1, 0.3, 1] }}
|
|
className="flex flex-col items-center"
|
|
>
|
|
<p
|
|
className="text-[10px] tracking-[0.38em] uppercase mb-8 font-sans font-medium"
|
|
style={{ color: "rgba(255,220,160,1)" }}
|
|
>
|
|
{siteSettings.location || t.hero.location}
|
|
</p>
|
|
|
|
{/* Logo */}
|
|
<img
|
|
src="/logo.avif"
|
|
alt="Kite Beach"
|
|
className="w-64 md:w-80 object-contain drop-shadow-lg"
|
|
style={{ filter: 'brightness(0) invert(1)' }}
|
|
/>
|
|
|
|
<div
|
|
className="flex items-center gap-3 my-5"
|
|
style={{ color: "rgba(255,190,100,0.35)" }}
|
|
>
|
|
<div className="h-px w-14" style={{ background: "currentColor" }} />
|
|
<span className="text-base">✦</span>
|
|
<div className="h-px w-14" style={{ background: "currentColor" }} />
|
|
</div>
|
|
|
|
<p
|
|
className="text-xs font-sans tracking-wide"
|
|
style={{ color: "rgba(255,218,168,0.5)" }}
|
|
>
|
|
{t.hero.subtitle}
|
|
</p>
|
|
</motion.div>
|
|
</div>
|
|
|
|
{/* wave transition */}
|
|
<div className="absolute bottom-0 left-0 right-0 translate-y-px">
|
|
<svg
|
|
viewBox="0 0 1440 52"
|
|
preserveAspectRatio="none"
|
|
className="w-full h-[52px]"
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
>
|
|
<path
|
|
d="M0 52L80 44C160 36 320 20 480 18C640 16 800 28 960 32C1120 36 1280 32 1360 30L1440 28V52H1360C1280 52 1120 52 960 52C800 52 640 52 480 52C320 52 160 52 80 52H0Z"
|
|
fill="#FAF8F3"
|
|
/>
|
|
</svg>
|
|
</div>
|
|
</header>
|
|
|
|
{/* ── Category Nav ─────────────────────────────── */}
|
|
<CategoryNav
|
|
categories={initialCategories}
|
|
activeCategoryId={activeCategoryId}
|
|
icons={CATEGORY_ICONS}
|
|
/>
|
|
|
|
{/* ── Menu Sections ────────────────────────────── */}
|
|
<main className="pb-28">
|
|
{initialCategories.map((category, index) => (
|
|
<motion.section
|
|
key={category.id}
|
|
id={category.id}
|
|
ref={(el) => { sectionRefs.current[index] = el; }}
|
|
className="pt-10 px-4 max-w-lg mx-auto scroll-mt-[62px]"
|
|
initial={{ opacity: 0, y: 14 }}
|
|
whileInView={{ opacity: 1, y: 0 }}
|
|
viewport={{ once: true, margin: "-50px" }}
|
|
transition={{ duration: 0.5, ease: [0.16, 1, 0.3, 1] }}
|
|
>
|
|
{/* section header */}
|
|
<div
|
|
className="flex items-center gap-2.5 mb-4 pb-3 border-b"
|
|
style={{ borderColor: "var(--sand-border)" }}
|
|
>
|
|
<span className="text-xl leading-none select-none">
|
|
{CATEGORY_ICONS[category.id] ?? "🍽️"}
|
|
</span>
|
|
<h2
|
|
className="font-display text-[22px] leading-tight font-semibold italic"
|
|
style={{ color: "var(--stone-ink)" }}
|
|
>
|
|
{category.title}
|
|
</h2>
|
|
</div>
|
|
|
|
<div>
|
|
{category.items.map((item, idx) => (
|
|
<MenuItem key={idx} item={item} onClick={() => setSelectedItem(item)} priority={index === 0 && idx < 5} />
|
|
))}
|
|
</div>
|
|
</motion.section>
|
|
))}
|
|
</main>
|
|
|
|
{/* ── Footer ───────────────────────────────────── */}
|
|
<footer
|
|
className="py-12 text-center"
|
|
style={{ background: "var(--hero-from)" }}
|
|
>
|
|
<div className="flex flex-col items-center gap-1">
|
|
{/* Footer Logo */}
|
|
<img
|
|
src="/logo.avif"
|
|
alt="Kite Beach"
|
|
className="w-36 object-contain opacity-70 mb-2"
|
|
style={{ filter: 'brightness(0) invert(1)' }}
|
|
/>
|
|
<p
|
|
className="text-[10px] font-sans tracking-[0.3em] uppercase mt-1"
|
|
style={{ color: "rgba(255,200,130,0.35)" }}
|
|
>
|
|
{siteSettings.location || t.hero.location}
|
|
</p>
|
|
<div
|
|
className="flex items-center gap-3 my-4"
|
|
style={{ color: "rgba(160,100,50,0.4)" }}
|
|
>
|
|
<div className="h-px w-10" style={{ background: "currentColor" }} />
|
|
<span className="text-xs">✦</span>
|
|
<div className="h-px w-10" style={{ background: "currentColor" }} />
|
|
</div>
|
|
<p
|
|
className="text-[11px] font-sans"
|
|
style={{ color: "rgba(120,90,60,0.5)" }}
|
|
>
|
|
{t.footer.copyright}
|
|
</p>
|
|
<div className="flex items-center gap-1.5 mt-3 text-[11px] font-sans" style={{ color: "rgba(180,140,110,0.8)" }}>
|
|
<span className="opacity-75">Created by</span>
|
|
<a
|
|
href="https://ayris.tech"
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
className="font-medium hover:opacity-100 transition-colors duration-200 hover:underline underline-offset-4"
|
|
style={{ color: "rgba(255,220,160,0.9)" }}
|
|
>
|
|
Ayris Tech
|
|
</a>
|
|
<span className="sr-only">
|
|
Legal AI & Solutions:{" "}
|
|
<a href="https://ayris.legal" rel="noopener">AyrisLegal</a>,{" "}
|
|
<a href="https://ayrislegal.com" rel="noopener">Ayris Legal AI</a>
|
|
The best local spots, hidden from plain sight. Marmaris Local{" "}
|
|
<a href="https://marmarislocal.com" rel="noopener">Marmaris Local</a>
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</footer>
|
|
|
|
{/* ── Modal ────────────────────────────────────── */}
|
|
<AnimatePresence>
|
|
{selectedItem && (
|
|
<motion.div
|
|
initial={{ opacity: 0 }}
|
|
animate={{ opacity: 1 }}
|
|
exit={{ opacity: 0 }}
|
|
className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/40 backdrop-blur-sm"
|
|
onClick={() => setSelectedItem(null)}
|
|
>
|
|
<motion.div
|
|
initial={{ y: 50, opacity: 0, scale: 0.95 }}
|
|
animate={{ y: 0, opacity: 1, scale: 1 }}
|
|
exit={{ y: 20, opacity: 0, scale: 0.95 }}
|
|
className="bg-white rounded-2xl overflow-hidden w-full max-w-sm shadow-xl"
|
|
onClick={(e) => e.stopPropagation()}
|
|
>
|
|
{selectedItem.image && (
|
|
<div className="w-full h-56 relative">
|
|
<Image src={selectedItem.image} alt={selectedItem.name} fill className="object-cover" sizes="(max-width: 768px) 100vw, 400px" priority />
|
|
<button
|
|
className="absolute top-3 right-3 bg-black/50 text-white w-8 h-8 rounded-full flex items-center justify-center backdrop-blur-md transition-colors hover:bg-black/70"
|
|
onClick={() => setSelectedItem(null)}
|
|
>
|
|
✕
|
|
</button>
|
|
</div>
|
|
)}
|
|
<div className="p-5">
|
|
<div className="flex justify-between items-start gap-4 mb-2">
|
|
<h3 className="text-xl font-semibold font-sans" style={{ color: "var(--stone-ink)" }}>
|
|
{selectedItem.name}
|
|
</h3>
|
|
<span className="shrink-0 text-lg font-semibold font-sans tabular-nums" style={{ color: "var(--amber)" }}>
|
|
{selectedItem.price}
|
|
</span>
|
|
</div>
|
|
<p className="text-[14px] leading-relaxed font-sans" style={{ color: "var(--stone-muted)" }}>
|
|
{selectedItem.description}
|
|
</p>
|
|
</div>
|
|
</motion.div>
|
|
</motion.div>
|
|
)}
|
|
</AnimatePresence>
|
|
</div>
|
|
);
|
|
}
|