/* =============================================================================
   lite-site-starter — app.jsx  (no-build, in-browser JSX via Babel standalone)

   THE METHOD, in one file:
   - bilingual i18n dictionary + toggle (default lang, persisted, Thai-capable fonts)
   - hero <video> (mp4 + webm) with poster + reduced-motion fallback
   - Lenis smooth scroll + IntersectionObserver reveals (reduced-motion safe)
   - reusable demo blocks: availability Calendar, Enquiry form (no backend),
     stylized Map with directions, sticky Call/LINE bar
   - everything client-side; no network for data (demo-grade by design)

   To brand a new site: edit BRAND, the DICT content, the CSS tokens in styles.css,
   and drop real assets into assets/. Keep the architecture; swap the content.
   ============================================================================ */
const { useState, useEffect, useRef, useCallback } = React;

const REDUCED = !!(window.matchMedia && window.matchMedia("(prefers-reduced-motion: reduce)").matches);

/* ----- per-project config -------------------------------------------------- */
const BRAND = "KEBABS AND FOOD";
const DEFAULT_LANG = "en";                 // "en" or "th"
const PHONE = "02-000-1234";               // demo sample — replace for a real client
const PHONE_TEL = "tel:020001234";         // demo sample
const LINE_ID = null;                      // [CONFIRM] — set to enable the LINE button
const MAPS_URL = "https://www.google.com/maps/search/?api=1&query=" + encodeURIComponent(BRAND);

/* ----- SEO / analytics config ---------------------------------------------
   The static SEO lives in index.html <head> (crawlable with zero JS). These
   two knobs drive the *enhancement* app.jsx does on top of it:
   - SITE_URL must match the origin used in index.html (canonical/hreflang/OG).
   - GA_ID is OFF by default (null) so the no-network demo contract (req005)
     stays green. Set "G-XXXXXXX" per site to enable GA4.                      */
const SITE_URL = "https://kebabs-and-food.vercel.app"; // matches index.html
const GA_ID = null;                              // [CONFIRM] "G-XXXXXXX" to enable GA4; null = no analytics
const OG_LOCALE = { en: "en_US", th: "th_TH" };

// Read a locale from ?lang= first (shareable/crawlable), then localStorage, then default.
function initialLang() {
  try {
    const p = new URLSearchParams(window.location.search).get("lang");
    if (p === "en" || p === "th") return p;
    return localStorage.getItem("site-lang") || DEFAULT_LANG;
  } catch { return DEFAULT_LANG; }
}

// On locale change, keep the crawlable metadata in sync without a reload.
function enhanceSeo(lang) {
  try {
    const base = SITE_URL.replace(/\/$/, "");
    const url = lang === DEFAULT_LANG ? base + "/" : base + "/?lang=" + lang;
    const set = (sel, attr, val) => { const el = document.querySelector(sel); if (el) el.setAttribute(attr, val); };
    document.documentElement.lang = lang;
    set('link[rel="canonical"]', "href", url);
    set('meta[property="og:url"]', "content", url);
    set('meta[property="og:locale"]', "content", OG_LOCALE[lang] || "en_US");
  } catch {}
}

// GA4, gated on GA_ID. Injected at runtime so the template ships analytics-free.
function loadAnalytics() {
  if (!GA_ID || document.getElementById("ga4-src")) return;
  const s = document.createElement("script");
  s.id = "ga4-src"; s.async = true;
  s.src = "https://www.googletagmanager.com/gtag/js?id=" + GA_ID;
  document.head.appendChild(s);
  window.dataLayer = window.dataLayer || [];
  window.gtag = function () { window.dataLayer.push(arguments); };
  window.gtag("js", new Date());
  window.gtag("config", GA_ID);
}

/* ----- i18n ---------------------------------------------------------------- */
const DICT = {
  en: {
    nav: { about: "About", offer: "Menu", gallery: "Gallery", availability: "Reservations", contact: "Contact", cta: "Book a table" },
    hero: { sub: "เคบับส์ แอนด์ ฟู้ด", tag: "Charcoal-grilled kebabs, fresh flatbread and mezze — made to order in the heart of Bangkok.", cta1: "Book a table", cta2: "Our story", scroll: "Scroll" },
    about: { eyebrow: "Our Story", title: "Fire, spice and bread — done the slow way",
      body: "Kebabs and Food started as a single charcoal grill and a family spice tin. Everything is marinated overnight, grilled over real charcoal, and folded into flatbread we bake through the day. No heat lamps, no shortcuts — just skewers coming off the fire when you order them." },
    chef: { quote: "“A kebab is judged in three seconds — the smell of the charcoal, the first tear of the bread, the first bite. We cook for those three seconds.”",
      name: "Chef Karim", role: "Founder · Head of the grill" },
    offer: { eyebrow: "Our Menu", title: "From the charcoal grill",
      items: [
        { tag: "SKEWERS", t: "Grilled over charcoal", d: "Marinated overnight, cooked to order.", dishes: [ { n: "Chicken shish kebab", p: "฿189" }, { n: "Lamb kofta skewer", p: "฿249" }, { n: "Mixed grill platter", p: "฿389" } ] },
        { tag: "WRAPS", t: "In fresh flatbread", d: "Baked through the day, filled to order.", dishes: [ { n: "Classic chicken wrap", p: "฿149" }, { n: "Beef doner wrap", p: "฿169" }, { n: "Falafel wrap (veg)", p: "฿129" } ] },
        { tag: "MEZZE", t: "For the table", d: "Small plates that start the meal right.", dishes: [ { n: "Hummus & warm bread", p: "฿99" }, { n: "Baba ganoush", p: "฿109" }, { n: "Crispy falafel (6)", p: "฿119" } ] },
      ], note: "Sample menu — demo site. Prices and dishes are placeholders for a real client's menu." },
    gallery: { eyebrow: "Gallery", title: "From the grill" },
    avail: { eyebrow: "Reservations", title: "Book a table",
      body: "Pick an open date (green) and send a reservation request. This is a demo — not wired to a real booking system.",
      open: "Open", hold: "Waitlist", booked: "Full", unavailable: "That date is full — try another.",
      selected: (d) => `${d} selected — fill in the form below.`, cont: "Continue to enquiry",
      asideT: "Walk-ins & groups", asideB: "Walk-ins are always welcome at the counter. For groups of six or more — or the long table by the grill — pick a date and send a request, and we'll call to confirm within the day.",
      months: ["January","February","March","April","May","June","July","August","September","October","November","December"],
      dow: ["Su","Mo","Tu","We","Th","Fr","Sa"] },
    form: { eyebrow: "Reserve", title: "Request a table", date: "Preferred date", noDate: "None yet (pick one above)",
      name: "Your name", contact: "Phone / email", type: "Occasion", count: "Party size", msg: "Anything else",
      opts: ["Dinner", "Lunch", "Group / event", "Other"], submit: "Request reservation",
      okT: "Thank you!", okB: "We have your request and will confirm by phone shortly. (Demo — nothing was actually sent.)",
      errReq: "This field is required", errCount: "Must be 1–500" },
    map: { eyebrow: "Getting here", title: "Find the smoke", addrT: BRAND, addrL: "88 Sample Road · Watthana, Bangkok (demo address)",
      dir: "Get directions", copy: "Copy address", copied: "Copied ✓",
      pins: ["Main entrance", "Motorbike parking", "BTS exit 3"], chips: ["BTS ~5 min walk", "Open 11:00–22:00 daily", "Delivery via LINE MAN"] },
    contact: { title: "Contact", phoneL: "Phone", lineL: "LINE", lineSoon: "LINE (coming soon)", call: "Call now", line: "Chat on LINE" },
    footer: { tag: "Charcoal-grill kebab house · Bangkok", rights: "© " + BRAND + " — demo site by saang studio · sample data" },
  },
  th: {
    nav: { about: "เกี่ยวกับเรา", offer: "เมนู", gallery: "แกลเลอรี", availability: "จองโต๊ะ", contact: "ติดต่อ", cta: "จองโต๊ะ" },
    hero: { sub: "Kebabs and Food", tag: "เคบับย่างถ่านหอมๆ แป้งแฟลตเบรดอบใหม่ และเมซเซ่ ทำสดทุกจาน ใจกลางกรุงเทพฯ", cta1: "จองโต๊ะ", cta2: "เรื่องราวของเรา", scroll: "เลื่อนลง" },
    about: { eyebrow: "เรื่องราวของเรา", title: "ไฟ ถ่าน เครื่องเทศ และแป้งอบใหม่ — แบบไม่รีบ",
      body: "Kebabs and Food เริ่มจากเตาถ่านหนึ่งเตากับกระปุกเครื่องเทศประจำครอบครัว ทุกไม้หมักค้างคืน ย่างบนถ่านจริง แล้วห่อด้วยแป้งที่เราอบสดตลอดวัน ไม่มีโคมอุ่นอาหาร ไม่มีทางลัด — เสียบไม้ขึ้นจากเตาเมื่อคุณสั่งเท่านั้น" },
    chef: { quote: "“เคบับถูกตัดสินในสามวินาที — กลิ่นถ่าน แป้งที่ฉีกครั้งแรก และคำแรกที่กัด เราทำอาหารเพื่อสามวินาทีนั้น”",
      name: "เชฟการีม", role: "ผู้ก่อตั้ง · หัวหน้าเตาย่าง" },
    offer: { eyebrow: "เมนูของเรา", title: "จากเตาถ่าน",
      items: [
        { tag: "ไม้ย่าง", t: "ย่างบนถ่านจริง", d: "หมักค้างคืน ย่างเมื่อสั่ง", dishes: [ { n: "ชิชเคบับไก่", p: "฿189" }, { n: "คอฟต้าแกะ", p: "฿249" }, { n: "รวมย่างจานใหญ่", p: "฿389" } ] },
        { tag: "แร็ป", t: "ในแป้งแฟลตเบรดอบสด", d: "อบใหม่ตลอดวัน ห่อเมื่อสั่ง", dishes: [ { n: "แร็ปไก่คลาสสิก", p: "฿149" }, { n: "แร็ปเนื้อโดเนอร์", p: "฿169" }, { n: "แร็ปฟาลาเฟล (มังสวิรัติ)", p: "฿129" } ] },
        { tag: "เมซเซ่", t: "สำหรับแชร์กลางโต๊ะ", d: "จานเล็กๆ ที่เปิดมื้อได้ดีที่สุด", dishes: [ { n: "ฮัมมูสกับขนมปังอุ่น", p: "฿99" }, { n: "บาบากานูช", p: "฿109" }, { n: "ฟาลาเฟลกรอบ (6 ชิ้น)", p: "฿119" } ] },
      ], note: "เมนูตัวอย่าง — เว็บไซต์เดโม่ ราคาและเมนูเป็นข้อมูลตัวอย่างสำหรับลูกค้าจริง" },
    gallery: { eyebrow: "แกลเลอรี", title: "จากเตาย่าง" },
    avail: { eyebrow: "จองโต๊ะ", title: "เช็ควันว่าง",
      body: "เลือกวันที่ว่าง (สีเขียว) แล้วส่งคำขอจองโต๊ะ — เดโม่นี้ยังไม่เชื่อมระบบจองจริง",
      open: "ว่าง", hold: "รอคิว", booked: "เต็ม", unavailable: "วันนี้เต็มแล้ว ลองเลือกวันอื่น",
      selected: (d) => `เลือกวันที่ ${d} แล้ว — กรอกฟอร์มด้านล่างได้เลย`, cont: "ดำเนินการสอบถาม",
      asideT: "วอล์กอินและหมู่คณะ", asideB: "วอล์กอินเชิญได้เสมอที่หน้าเคาน์เตอร์ ส่วนกลุ่ม 6 คนขึ้นไป หรืออยากได้โต๊ะยาวข้างเตาย่าง เลือกวันแล้วส่งคำขอไว้ เราจะโทรยืนยันภายในวันเดียวกัน",
      months: ["มกราคม","กุมภาพันธ์","มีนาคม","เมษายน","พฤษภาคม","มิถุนายน","กรกฎาคม","สิงหาคม","กันยายน","ตุลาคม","พฤศจิกายน","ธันวาคม"],
      dow: ["อา","จ","อ","พ","พฤ","ศ","ส"] },
    form: { eyebrow: "จองโต๊ะ", title: "ส่งคำขอจองโต๊ะ", date: "วันที่ต้องการ", noDate: "ยังไม่เลือก (เลือกจากด้านบน)",
      name: "ชื่อของคุณ", contact: "เบอร์โทร / อีเมล", type: "โอกาส", count: "จำนวนคน", msg: "ข้อความเพิ่มเติม",
      opts: ["มื้อเย็น", "มื้อกลางวัน", "กลุ่ม / งานเลี้ยง", "อื่นๆ"], submit: "ส่งคำขอจอง",
      okT: "ขอบคุณค่ะ!", okB: "เราได้รับคำขอแล้ว จะโทรยืนยันโดยเร็ว (เดโม่ — ยังไม่มีการส่งข้อมูลจริง)",
      errReq: "กรุณากรอกข้อมูลนี้", errCount: "ต้องเป็น 1–500" },
    map: { eyebrow: "เดินทาง", title: "ตามกลิ่นถ่านมาเจอเรา", addrT: BRAND, addrL: "88 ถนนตัวอย่าง · วัฒนา กรุงเทพฯ (ที่อยู่ตัวอย่าง)",
      dir: "นำทาง", copy: "คัดลอกที่อยู่", copied: "คัดลอกแล้ว ✓",
      pins: ["ทางเข้าหลัก", "ที่จอดมอเตอร์ไซค์", "BTS ทางออก 3"], chips: ["เดินจาก BTS ~5 นาที", "เปิด 11:00–22:00 ทุกวัน", "สั่งผ่าน LINE MAN"] },
    contact: { title: "ติดต่อ", phoneL: "โทร", lineL: "LINE", lineSoon: "LINE (เร็วๆ นี้)", call: "โทรเลย", line: "แชต LINE" },
    footer: { tag: "ร้านเคบับเตาถ่าน · กรุงเทพฯ", rights: "© " + BRAND + " — เว็บเดโม่โดย saang studio · ข้อมูลตัวอย่าง" },
  },
};
// expose for tests (i18n completeness, reduced-motion)
window.__DICT = DICT;
window.__REDUCED = REDUCED;

/* ----- canned availability (deterministic, stable across reloads) ---------- */
function dateState(y, m, d) {
  const r = (y * 12 + m) * 31 + d;
  const v = (r * 2654435761) % 97;
  if (v < 28) return "booked";
  if (v < 50) return "hold";
  return "open";
}

/* ----- utils --------------------------------------------------------------- */
function goto(id) {
  const el = document.getElementById(id);
  if (!el) return;
  if (window.__lenis) window.__lenis.scrollTo(el, { offset: -64 });
  else el.scrollIntoView({ behavior: REDUCED ? "auto" : "smooth" });
}
const todayMidnight = () => { const n = new Date(); n.setHours(0, 0, 0, 0); return n; };

/* ----- Reveal -------------------------------------------------------------- */
function Reveal({ children, className = "", tag = "div", id, style }) {
  const ref = useRef(null);
  const [shown, setShown] = useState(REDUCED);
  useEffect(() => {
    if (REDUCED || shown) return;
    const el = ref.current;
    if (!el || !("IntersectionObserver" in window)) { setShown(true); return; }
    const io = new IntersectionObserver(
      (es) => es.forEach((e) => { if (e.isIntersecting) { setShown(true); io.disconnect(); } }),
      { root: null, rootMargin: "0px 0px -20% 0px", threshold: 0.01 }
    );
    io.observe(el);
    return () => io.disconnect();
  }, [shown]);
  const Tag = tag;
  return <Tag id={id} ref={ref} style={style} className={"reveal " + (shown ? "is-in " : "") + className}>{children}</Tag>;
}

/* ----- Emblem (swap for a real logo) --------------------------------------- */
function Emblem({ className }) {
  return (
    <svg className={className} viewBox="0 0 64 64" fill="none" aria-hidden="true">
      <circle cx="32" cy="32" r="26" stroke="var(--accent)" strokeWidth="3" />
      <path d="M22 38l10-18 10 18z" fill="var(--accent)" />
    </svg>
  );
}

/* ----- Nav ----------------------------------------------------------------- */
function Nav({ t, lang, toggleLang }) {
  const [scrolled, setScrolled] = useState(false);
  useEffect(() => {
    const onScroll = () => setScrolled(window.scrollY > 48);
    onScroll();
    window.addEventListener("scroll", onScroll, { passive: true });
    return () => window.removeEventListener("scroll", onScroll);
  }, []);
  const links = ["about", "offer", "gallery", "availability", "contact"];
  return (
    <nav className="nav" data-scrolled={scrolled}>
      <a className="nav__brand" href="#top" onClick={(e) => { e.preventDefault(); goto("top"); }}>
        <Emblem className="nav__emblem" />
        <span className="nav__name">{BRAND}</span>
      </a>
      <div className="nav__links">
        {links.map((k) => <a key={k} href={"#" + k} onClick={(e) => { e.preventDefault(); goto(k); }}>{t.nav[k]}</a>)}
      </div>
      <div className="nav__right">
        <button className="langtoggle" onClick={toggleLang} aria-label="Switch language">{lang === "th" ? "EN" : "ไทย"}</button>
        <button className="btn btn--accent" onClick={() => goto("availability")}>{t.nav.cta}</button>
      </div>
    </nav>
  );
}

/* ----- Hero ---------------------------------------------------------------- */
function Hero({ t }) {
  const videoRef = useRef(null);
  const [failed, setFailed] = useState(false);
  useEffect(() => {
    if (REDUCED) return;
    const v = videoRef.current;
    if (!v) return;
    v.muted = true;
    const p = v.play();
    if (p && p.catch) p.catch(() => setFailed(true));
  }, []);
  const showPoster = REDUCED || failed;
  return (
    <header className="hero" id="top">
      <div className="hero__media">
        {showPoster ? (
          <img src="assets/hero-poster.jpg" onError={(e) => { e.target.src = "assets/hero-poster.svg"; }} alt="" />
        ) : (
          <video ref={videoRef} autoPlay muted loop playsInline poster="assets/hero-poster.svg" preload="auto" onError={() => setFailed(true)}>
            <source src="assets/hero.mp4" type="video/mp4" onError={() => setFailed(true)} />
            <source src="assets/hero.webm" type="video/webm" onError={() => setFailed(true)} />
          </video>
        )}
      </div>
      <div className="hero__scrim" />
      <div className="hero__inner">
        <Emblem className="hero__emblem" />
        <h1 className="hero__name">{BRAND}</h1>
        <div className="hero__sub">{t.hero.sub}</div>
        <p className="hero__tag">{t.hero.tag}</p>
        <div className="hero__cta">
          <button className="btn btn--accent" onClick={() => goto("availability")}>{t.hero.cta1}</button>
          <button className="btn btn--ghost" onClick={() => goto("about")}>{t.hero.cta2}</button>
        </div>
      </div>
    </header>
  );
}

/* ----- About + Offer + Gallery (content sections) -------------------------- */
function About({ t }) {
  return (
    <section className="section" id="about">
      <Reveal className="wrap">
        <p className="eyebrow">{t.about.eyebrow}</p>
        <h2 className="lead">{t.about.title}<span className="splash" aria-hidden="true" /></h2>
        <p className="muted" style={{ maxWidth: 720, marginTop: 16, fontSize: 18 }}>{t.about.body}</p>
      </Reveal>
    </section>
  );
}

/* ----- Chef band (JHOL dark band) ------------------------------------------ */
function Chef({ t }) {
  return (
    <section className="section band--dark chef" id="chef">
      <Reveal className="wrap">
        <p className="chef__quote">{t.chef.quote}</p>
        <div className="chef__name">{t.chef.name}</div>
        <div className="chef__role">{t.chef.role}</div>
      </Reveal>
    </section>
  );
}

function Offer({ t }) {
  return (
    <section className="section section--tint" id="offer">
      <div className="wrap">
        <Reveal>
          <p className="eyebrow">{t.offer.eyebrow}</p>
          <h2 style={{ fontSize: "clamp(28px,4vw,44px)", marginBottom: 28 }}>{t.offer.title}<span className="splash" aria-hidden="true" /></h2>
        </Reveal>
        <div className="grid grid--3">
          {t.offer.items.map((it, i) => (
            <Reveal key={i} className="card">
              <span className="confirm" style={{ borderStyle: "solid", marginBottom: 12 }}>{it.tag}</span>
              <h3 style={{ fontSize: 24, margin: "8px 0" }}>{it.t}</h3>
              <p className="muted" style={{ margin: "0 0 10px" }}>{it.d}</p>
              <div>
                {it.dishes.map((d2, j) => (
                  <div key={j} className="menuitem"><span className="n">{d2.n}</span><span className="p">{d2.p}</span></div>
                ))}
              </div>
            </Reveal>
          ))}
        </div>
        <Reveal><p style={{ marginTop: 26, fontFamily: "var(--serif)", fontSize: 19, color: "var(--accent-2)" }}>{t.offer.note}</p></Reveal>
      </div>
    </section>
  );
}
function Gallery({ t }) {
  // placeholder tiles; replace with real <img src="assets/..."> when you have photos
  const tiles = [
    { f: "none", t: "scale(1.0)" },
    { f: "hue-rotate(18deg) saturate(1.15)", t: "scale(1.35) translate(6%, -6%)" },
    { f: "hue-rotate(-14deg) brightness(1.08)", t: "scale(1.6) translate(-10%, 8%)" },
    { f: "sepia(.25) saturate(1.2)", t: "scale(1.2) translate(8%, 6%)" },
  ];
  return (
    <section className="section" id="gallery">
      <div className="wrap">
        <Reveal>
          <p className="eyebrow">{t.gallery.eyebrow}</p>
          <h2 style={{ fontSize: "clamp(28px,4vw,44px)", marginBottom: 26 }}>{t.gallery.title}</h2>
        </Reveal>
        <Reveal className="grid grid--2">
          {tiles.map((i, idx) => (
            <figure key={idx} style={{ margin: 0, borderRadius: "var(--r)", overflow: "hidden", boxShadow: "var(--shadow)" }}>
              <img src="assets/hero-poster.svg" alt={"Grill artwork " + (idx + 1) + " — demo placeholder, replace with real photos"} style={{ width: "100%", aspectRatio: "3/2", objectFit: "cover", filter: i.f, transform: i.t }} />
            </figure>
          ))}
        </Reveal>
      </div>
    </section>
  );
}

/* ----- Calendar ------------------------------------------------------------ */
function Calendar({ t, selected, onSelect, onMessage }) {
  const today = todayMidnight();
  const [view, setView] = useState({ y: today.getFullYear(), m: today.getMonth() });
  const minTotal = today.getFullYear() * 12 + today.getMonth();
  const maxTotal = minTotal + 5;
  const curTotal = view.y * 12 + view.m;
  const firstDow = new Date(view.y, view.m, 1).getDay();
  const daysIn = new Date(view.y, view.m + 1, 0).getDate();
  const cells = [];
  for (let i = 0; i < firstDow; i++) cells.push(null);
  for (let d = 1; d <= daysIn; d++) cells.push(d);

  const move = (delta) => {
    const nt = curTotal + delta;
    if (nt < minTotal || nt > maxTotal) return;
    setView({ y: Math.floor(nt / 12), m: nt % 12 });
  };
  const pick = (d, state, past) => {
    if (past || !d) return;
    if (state !== "open") { onMessage(t.avail.unavailable, false); return; }
    const iso = `${view.y}-${String(view.m + 1).padStart(2, "0")}-${String(d).padStart(2, "0")}`;
    const label = `${d} ${t.avail.months[view.m]} ${view.y}`;
    onSelect({ iso, label });
    onMessage(t.avail.selected(label), true);
  };

  return (
    <div className="cal" role="group" aria-label={t.avail.title}>
      <div className="cal__head">
        <button className="cal__nav" onClick={() => move(-1)} disabled={curTotal <= minTotal} aria-label="Previous month">‹</button>
        <div className="cal__title">{t.avail.months[view.m]} {view.y}</div>
        <button className="cal__nav" onClick={() => move(1)} disabled={curTotal >= maxTotal} aria-label="Next month">›</button>
      </div>
      <div className="cal__dow">{t.avail.dow.map((d, i) => <span key={i}>{d}</span>)}</div>
      <div className="cal__grid">
        {cells.map((d, i) => {
          if (!d) return <div key={i} className="cal__day" data-empty="true" aria-hidden="true" />;
          const cellDate = new Date(view.y, view.m, d); cellDate.setHours(0, 0, 0, 0);
          const past = cellDate < today;
          const state = past ? "past" : dateState(view.y, view.m, d);
          const iso = `${view.y}-${String(view.m + 1).padStart(2, "0")}-${String(d).padStart(2, "0")}`;
          const isSel = selected && selected.iso === iso;
          const label = past ? `${d} (past)` : `${d} ${t.avail.months[view.m]} — ${t.avail[state]}`;
          return (
            <button key={i} className="cal__day" data-state={past ? undefined : state} data-past={past ? "true" : undefined}
              disabled={past} aria-pressed={isSel ? "true" : "false"} aria-label={label} onClick={() => pick(d, state, past)}>
              {d}{!past && <span className="dot" />}
            </button>
          );
        })}
      </div>
      <div className="cal__legend">
        <span><i style={{ background: "#4f9d62" }} />{t.avail.open}</span>
        <span><i style={{ background: "var(--accent)" }} />{t.avail.hold}</span>
        <span><i style={{ background: "#cE8E8B" }} />{t.avail.booked}</span>
      </div>
    </div>
  );
}

function Availability({ t, selected, onSelect }) {
  const [msg, setMsg] = useState({ text: "", ok: false });
  return (
    <section className="section" id="availability">
      <div className="wrap">
        <Reveal>
          <p className="eyebrow">{t.avail.eyebrow}</p>
          <h2 style={{ fontSize: "clamp(28px,4vw,44px)", marginBottom: 10 }}>{t.avail.title}</h2>
          <p className="muted" style={{ maxWidth: 560, marginBottom: 28 }}>{t.avail.body}</p>
        </Reveal>
        <div className="grid grid--2" style={{ alignItems: "start" }}>
          <Reveal>
            <Calendar t={t} selected={selected} onSelect={onSelect} onMessage={(text, ok) => setMsg({ text, ok })} />
            <div className="cal__msg" data-ok={msg.ok} role="status">{msg.text}</div>
          </Reveal>
          <Reveal>
            <div className="card">
              <h3 style={{ fontSize: 24, marginBottom: 10 }}>{t.avail.asideT}</h3>
              <p className="muted">{t.avail.asideB}</p>
              <button className="btn btn--accent" onClick={() => goto("enquire")} style={{ marginTop: 8 }}>{t.avail.cont}</button>
            </div>
          </Reveal>
        </div>
      </div>
    </section>
  );
}

/* ----- Enquiry (demo, no backend) ------------------------------------------ */
function Enquiry({ t, selected }) {
  const [submitted, setSubmitted] = useState(false);
  const [errors, setErrors] = useState({});
  const onSubmit = (e) => {
    e.preventDefault();
    const f = e.target;
    const name = f.name.value.trim();
    const contact = f.contact.value.trim();
    const countRaw = f.count.value.trim();
    const count = Number(countRaw);
    const next = {};
    if (!name) next.name = t.form.errReq;
    if (!contact) next.contact = t.form.errReq;
    if (!countRaw || !Number.isInteger(count) || count < 1 || count > 500) next.count = t.form.errCount;
    setErrors(next);
    if (Object.keys(next).length) {
      const firstKey = ["name", "contact", "count"].find((k) => next[k]);
      const elt = firstKey && document.getElementById("f-" + firstKey);
      if (elt) elt.focus();
      return;
    }
    setSubmitted(true); // demo only — NO network
  };
  if (submitted) {
    return (
      <section className="section section--tint band--dark" id="enquire">
        <div className="wrap">
          <Reveal className="form__success" role="status">
            <h3>{t.form.okT}</h3>
            <p style={{ margin: 0 }}>{t.form.okB}</p>
          </Reveal>
        </div>
      </section>
    );
  }
  return (
    <section className="section section--tint band--dark" id="enquire">
      <div className="wrap">
        <Reveal>
          <p className="eyebrow">{t.form.eyebrow}</p>
          <h2 style={{ fontSize: "clamp(28px,4vw,44px)", marginBottom: 22 }}>{t.form.title}</h2>
        </Reveal>
        <Reveal>
          <form className="form" onSubmit={onSubmit} noValidate>
            <div className="field">
              <label>{t.form.date}</label>
              <input name="date" readOnly value={selected ? selected.label : ""} placeholder={t.form.noDate} />
            </div>
            <div className="two">
              <div className="field" data-invalid={errors.name ? "true" : undefined}>
                <label htmlFor="f-name">{t.form.name}</label>
                <input id="f-name" name="name" type="text" />
                <div className="field__err">{errors.name || ""}</div>
              </div>
              <div className="field" data-invalid={errors.contact ? "true" : undefined}>
                <label htmlFor="f-contact">{t.form.contact}</label>
                <input id="f-contact" name="contact" type="text" />
                <div className="field__err">{errors.contact || ""}</div>
              </div>
            </div>
            <div className="two">
              <div className="field">
                <label htmlFor="f-type">{t.form.type}</label>
                <select id="f-type" name="type" defaultValue={t.form.opts[0]}>
                  {t.form.opts.map((o, i) => <option key={i} value={o}>{o}</option>)}
                </select>
              </div>
              <div className="field" data-invalid={errors.count ? "true" : undefined}>
                <label htmlFor="f-count">{t.form.count}</label>
                <input id="f-count" name="count" type="number" min="1" max="500" defaultValue="50" />
                <div className="field__err">{errors.count || ""}</div>
              </div>
            </div>
            <div className="field">
              <label htmlFor="f-msg">{t.form.msg}</label>
              <textarea id="f-msg" name="msg" rows="3" />
            </div>
            <button className="btn btn--accent btn--block" type="submit">{t.form.submit}</button>
          </form>
        </Reveal>
      </div>
    </section>
  );
}

/* ----- Map ----------------------------------------------------------------- */
function StylizedMap({ t }) {
  const pins = [
    { x: 56, y: 52, label: t.map.pins[0] },
    { x: 70, y: 64, label: t.map.pins[1] },
    { x: 34, y: 44, label: t.map.pins[2] },
  ];
  return (
    <div className="mapcanvas" aria-label="Stylized map">
      <svg viewBox="0 0 100 70" preserveAspectRatio="xMidYMid slice" aria-hidden="true">
        <rect width="100" height="70" fill="#EAF0E6" />
        <rect x="8" y="10" width="26" height="18" rx="2" fill="#E3EAD9" />
        <rect x="64" y="40" width="30" height="22" rx="2" fill="#E3EAD9" />
        <path d="M0 24 C 25 20, 45 34, 70 30 S 100 26, 100 30 L100 36 C 75 36, 50 40, 25 38 S 0 32, 0 32 Z" fill="#BBD7E6" opacity=".8" />
        <path d="M0 50 H100" stroke="#fff" strokeWidth="3.2" />
        <path d="M48 0 V70" stroke="#fff" strokeWidth="2.4" />
      </svg>
      {pins.map((p, i) => (
        <button key={i} className="pin" style={{ left: p.x + "%", top: p.y + "%" }} aria-label={p.label}>
          <svg className="pin__dot" viewBox="0 0 24 24" aria-hidden="true">
            <path d="M12 2C7.6 2 4 5.6 4 10c0 6 8 12 8 12s8-6 8-12c0-4.4-3.6-8-8-8Z" fill={i === 0 ? "var(--accent-2)" : "var(--accent)"} />
            <circle cx="12" cy="10" r="3" fill="#fff" />
          </svg>
          <span className="pin__label">{p.label}</span>
        </button>
      ))}
    </div>
  );
}
function MapBlock({ t }) {
  const [copied, setCopied] = useState(false);
  const address = t.map.addrL.replace(/\s*\[.*?\]\s*/g, "");
  const copy = async () => {
    try {
      if (navigator.clipboard && navigator.clipboard.writeText) await navigator.clipboard.writeText(address);
      else throw new Error("no clipboard");
    } catch {
      const el = document.getElementById("addr-line");
      if (el && window.getSelection) {
        const range = document.createRange(); range.selectNodeContents(el);
        const sel = window.getSelection(); sel.removeAllRanges(); sel.addRange(range);
      }
    }
    setCopied(true); setTimeout(() => setCopied(false), 2000);
  };
  return (
    <section className="section" id="contact">
      <div className="wrap">
        <Reveal>
          <p className="eyebrow">{t.map.eyebrow}</p>
          <h2 style={{ fontSize: "clamp(28px,4vw,44px)", marginBottom: 24 }}>{t.map.title}</h2>
        </Reveal>
        <Reveal className="mapwrap">
          <StylizedMap t={t} />
          <div className="mapside">
            <div className="addr">
              <h3 style={{ fontSize: 20, marginBottom: 6 }}>{t.map.addrT}</h3>
              <p id="addr-line" className="muted" style={{ marginBottom: 12 }}>{t.map.addrL}</p>
              <div style={{ display: "flex", gap: 10, flexWrap: "wrap" }}>
                <a className="btn btn--accent" href={MAPS_URL} target="_blank" rel="noopener noreferrer">{t.map.dir}</a>
                <button className="copybtn" data-copied={copied} onClick={copy}>{copied ? t.map.copied : t.map.copy}</button>
              </div>
            </div>
            <div className="chips">{t.map.chips.map((c, i) => <span key={i} className="chip">{c}</span>)}</div>
            <div className="addr">
              <h3 style={{ fontSize: 18 }}>{t.contact.title}</h3>
              <p style={{ margin: "6px 0" }}>{t.contact.phoneL}: <a href={PHONE_TEL}>{PHONE}</a></p>
              <p style={{ margin: 0 }} className="muted">{t.contact.lineL}: <span className="confirm">{t.contact.lineSoon}</span></p>
            </div>
          </div>
        </Reveal>
      </div>
    </section>
  );
}

/* ----- Sticky + Footer ----------------------------------------------------- */
function StickyCta({ t }) {
  return (
    <div className="sticky" role="region" aria-label="Quick contact">
      <a className="call" href={PHONE_TEL}>📞 {t.contact.call}</a>
      {LINE_ID ? (
        <a className="line" href={`https://line.me/ti/p/~${LINE_ID}`} target="_blank" rel="noopener noreferrer">💬 {t.contact.line}</a>
      ) : (
        <a className="line" data-confirm="true" href={PHONE_TEL} aria-label="LINE coming soon, call instead">💬 {t.contact.lineSoon}</a>
      )}
    </div>
  );
}
function Footer({ t }) {
  return (
    <footer className="footer">
      <div className="nav__name" style={{ fontSize: 20, letterSpacing: ".16em" }}>{BRAND}</div>
      <p style={{ margin: "10px 0 6px", opacity: .85 }}>{t.footer.tag}</p>
      <p style={{ margin: 0, opacity: .7, fontSize: 13 }}>{t.footer.rights} · {PHONE}</p>
      <span className="demobadge">Demo · sample data</span>
    </footer>
  );
}

/* ----- App ----------------------------------------------------------------- */
function App() {
  const [lang, setLang] = useState(initialLang);
  const [selected, setSelected] = useState(null);
  const t = DICT[lang];
  useEffect(() => { loadAnalytics(); }, []);
  useEffect(() => {
    try { localStorage.setItem("site-lang", lang); } catch {}
    // keep the URL locale in sync so it's shareable/crawlable (?lang=th), no reload
    try {
      const params = new URLSearchParams(window.location.search);
      if (lang === DEFAULT_LANG) params.delete("lang");
      else params.set("lang", lang);
      const qs = params.toString();
      window.history.replaceState(null, "", window.location.pathname + (qs ? "?" + qs : "") + window.location.hash);
    } catch {}
    enhanceSeo(lang);
  }, [lang]);
  useEffect(() => {
    if (REDUCED || typeof Lenis === "undefined") return;
    const lenis = new Lenis({ duration: 1.1, smoothWheel: true });
    window.__lenis = lenis;
    let raf;
    const loop = (time) => { lenis.raf(time); raf = requestAnimationFrame(loop); };
    raf = requestAnimationFrame(loop);
    return () => { cancelAnimationFrame(raf); lenis.destroy(); window.__lenis = null; };
  }, []);
  const toggleLang = useCallback(() => setLang((l) => (l === "th" ? "en" : "th")), []);
  return (
    <div className="has-sticky">
      <Nav t={t} lang={lang} toggleLang={toggleLang} />
      <Hero t={t} />
      <About t={t} />
      <Chef t={t} />
      <Offer t={t} />
      <Gallery t={t} />
      <Availability t={t} selected={selected} onSelect={setSelected} />
      <Enquiry t={t} selected={selected} />
      <MapBlock t={t} />
      <StickyCta t={t} />
      <Footer t={t} />
    </div>
  );
}
ReactDOM.createRoot(document.getElementById("root")).render(<App />);
