/* =============================================================================
   Siam Vow Estate — app.jsx  (no-build, in-browser JSX via Babel standalone)
   Single file by design: babel-standalone runs each <script> in its own scope,
   so keeping everything here avoids cross-file global wiring.
   ============================================================================ */
const { useState, useEffect, useRef, useCallback } = React;

const REDUCED = !!(window.matchMedia && window.matchMedia("(prefers-reduced-motion: reduce)").matches);
const PHONE = "086-345-4255";
const PHONE_TEL = "tel:0863454255";
const LINE_ID = null; // [CONFIRM] — owner has not given a LINE ID yet
const MAPS_URL =
  "https://www.google.com/maps/search/?api=1&query=" +
  encodeURIComponent("Siam Vow Estate จตุโชติ 14 กรุงเทพ"); // [CONFIRM] exact pin

/* ---------------------------------------------------------------- i18n ---- */
const DICT = {
  th: {
    nav: { spaces: "สถานที่", packages: "แพ็กเกจ", gallery: "ผลงาน", availability: "เช็ควันว่าง", contact: "ติดต่อ", book: "จองวันว่าง" },
    hero: {
      th: "สยาม วาว เอสเตท",
      desc: "เวดดิ้งเอสเตท และโลเคชันถ่ายทำ ใจกลางกรุงเทพฯ",
      tag: "งานแต่งงานในฝันของคุณเกิดขึ้นจริงได้ · ถ่ายทำระดับโปร — ฮอลล์หรูปรับได้ทุกธีม พร้อมของฟรีที่จัดเต็มให้กับเจ้าบ่าวและเจ้าสาว",
      cta1: "เช็ควันว่าง", cta2: "ชมสถานที่", scroll: "เลื่อนลง",
    },
    sig: {
      eyebrow: "ที่เดียวจบ",
      title: "ฮอลล์หรูที่กลายเป็นงานแต่งในฝันของคุณ",
      body: "พื้นที่โครงสร้างสูงโปร่ง โคมระย้าระดับแกรนด์ และจอโปรเจกเตอร์ขนาดใหญ่ ปรับแต่งได้ทั้งงานเลี้ยงหรูหรา พิธีไทย-จีน ไปจนถึงโลเคชันถ่ายทำมืออาชีพ — ครบในที่เดียว",
    },
    perks: {
      eyebrow: "ของฟรี มีที่นี่", title: "สามสิ่งที่เราจัดให้ฟรี",
      items: [
        { free: "ฟรี", t: "ช่างภาพ + ถ่ายพรีเวดดิ้ง", d: "ทีมช่างภาพพาบ่าวสาวถ่ายรูปในสถานที่จริง ฟรี" },
        { free: "ฟรี", t: "ใช้สถานที่จัดงาน", d: "บ่าวสาวจัดงานแต่ง ใช้พื้นที่เอสเตทได้ ฟรี" },
        { free: "ฟรี", t: "ออร์แกไนเซอร์", d: "ทีมจัดงานแต่งดูแลคิวงานให้ ฟรี" },
      ],
      urgency: "จองคิวก่อนเต็ม · ของฟรีมีไม่นาน",
    },
    spaces: {
      eyebrow: "สถานที่", title: "พื้นที่เดียว เปลี่ยนได้ทุกบรรยากาศ",
      body: "จากฮอลล์ดิบสไตล์ลอฟต์ สู่งานเลี้ยงดอกไม้อลังการ และมุมพิธีไทย-จีนสุดคลาสสิก",
      cap1: "ฮอลล์โครงสร้างสูง · โลเคชันถ่ายทำ", cap2: "มุมพิธีไทย-จีน · ผ้าม่านชมพู-ทอง",
    },
    pkg: {
      eyebrow: "แพ็กเกจ", title: "แพ็กเกจงานแต่ง", note: "ราคา/รายละเอียดรอยืนยันจากทีมงาน",
      tiers: [
        { t: "Intimate", d: "งานพิธีเล็ก อบอุ่น เน้นครอบครัว", p: "สอบถาม" },
        { t: "Signature", d: "งานเลี้ยงเต็มรูปแบบ พร้อมตกแต่งดอกไม้", p: "สอบถาม" },
        { t: "Cinematic", d: "จัดเต็มพร้อมโลเคชันถ่ายทำระดับโปร", p: "สอบถาม" },
      ],
    },
    gallery: { eyebrow: "ผลงาน", title: "บรรยากาศงานจริง" },
    avail: {
      eyebrow: "เช็ควันว่าง", title: "วันของคุณว่างไหม?",
      body: "เลือกวันที่ว่าง (จุดเขียว) แล้วกรอกฟอร์มสอบถาม — เดโม่นี้ยังไม่เชื่อมระบบจองจริง",
      open: "ว่าง", hold: "มีผู้สนใจ", booked: "ถูกจองแล้ว",
      unavailable: "วันนี้ไม่ว่าง ลองเลือกวันอื่น",
      selected: (d) => `เลือกวันที่ ${d} แล้ว — กรอกฟอร์มด้านล่างได้เลย`,
      cont: "ดำเนินการสอบถาม",
      months: ["มกราคม","กุมภาพันธ์","มีนาคม","เมษายน","พฤษภาคม","มิถุนายน","กรกฎาคม","สิงหาคม","กันยายน","ตุลาคม","พฤศจิกายน","ธันวาคม"],
      dow: ["อา","จ","อ","พ","พฤ","ศ","ส"],
    },
    form: {
      eyebrow: "สอบถาม", title: "ส่งคำถามถึงเรา",
      date: "วันที่สนใจ", noDate: "ยังไม่เลือก (เลือกจากปฏิทินด้านบน)",
      name: "ชื่อบ่าวสาว", contact: "เบอร์โทร / LINE", ceremony: "ประเภทพิธี", guests: "จำนวนแขก", msg: "ข้อความเพิ่มเติม",
      cOpts: ["งานเลี้ยงฉลอง","พิธีไทย-จีน","พิธีเช้า + เลี้ยงเย็น","ถ่ายภาพ/โลเคชัน"],
      submit: "ส่งคำถาม",
      okT: "ขอบคุณมากค่ะ! 💌", okB: "เราได้รับคำถามของคุณแล้ว ทีมงานจะติดต่อกลับทาง LINE/โทรศัพท์โดยเร็วที่สุด (นี่คือเดโม่ — ยังไม่มีการส่งข้อมูลจริง)",
      errReq: "กรุณากรอกข้อมูลนี้", errGuests: "จำนวนแขกต้องเป็น 1–500",
    },
    map: {
      eyebrow: "เดินทาง", title: "หาเราเจอง่าย",
      addrT: "สยาม วาว เอสเตท", addrL: "ซอยจตุโชติ 14 · กรุงเทพฯ  [ที่อยู่เต็มรอยืนยัน]",
      dir: "นำทาง (Google Maps)", copy: "คัดลอกที่อยู่", copied: "คัดลอกแล้ว ✓",
      pins: ["เอสเตท", "ที่จอดรถ", "ปากซอยจตุโชติ 14"],
      chips: ["ดอนเมือง ~20 นาที", "รามอินทรา ~15 นาที", "ทางด่วน ~10 นาที"],
    },
    contact: { title: "ติดต่อจองคิว", phoneL: "โทร", lineL: "LINE", lineSoon: "LINE (เร็วๆ นี้)", call: "โทรเลย", line: "แชต LINE" },
    footer: { tag: "เวดดิ้งเอสเตท และโลเคชันถ่ายทำ · กรุงเทพฯ", rights: "© Siam Vow Estate — เดโม่" },
  },
  en: {
    nav: { spaces: "Spaces", packages: "Packages", gallery: "Gallery", availability: "Availability", contact: "Contact", book: "Check dates" },
    hero: {
      th: "สยาม วาว เอสเตท",
      desc: "Wedding estate & professional film location in Bangkok",
      tag: "A dream wedding, shot like a film — one transformable luxe hall, with real perks thrown in free for couples.",
      cta1: "Check availability", cta2: "See the spaces", scroll: "Scroll",
    },
    sig: {
      eyebrow: "All in one place",
      title: "A grand hall that becomes your dream wedding",
      body: "Double-height steel, statement chandeliers and a cinema-scale screen — dressed into an opulent banquet, a classic Thai-Chinese ceremony, or a professional shoot location. All under one roof.",
    },
    perks: {
      eyebrow: "Free, right here", title: "Three things we throw in free",
      items: [
        { free: "FREE", t: "Photographer + pre-wedding shoot", d: "Our team shoots the couple on-site, on us." },
        { free: "FREE", t: "Use of the venue", d: "Couples host their wedding in the estate, free." },
        { free: "FREE", t: "Event organizer", d: "Our planners run the whole day for you, free." },
      ],
      urgency: "Book before it fills — the perks won't last.",
    },
    spaces: {
      eyebrow: "The spaces", title: "One space, every mood",
      body: "From a raw loft-style hall to a flower-drenched banquet and a classic Thai-Chinese ceremony corner.",
      cap1: "The high-steel hall · film location", cap2: "Thai-Chinese ceremony · pink & gold drapes",
    },
    pkg: {
      eyebrow: "Packages", title: "Wedding packages", note: "Pricing & inclusions to be confirmed by the team",
      tiers: [
        { t: "Intimate", d: "A small, warm, family-first ceremony", p: "Enquire" },
        { t: "Signature", d: "A full banquet with floral styling", p: "Enquire" },
        { t: "Cinematic", d: "The works, plus a pro shoot location", p: "Enquire" },
      ],
    },
    gallery: { eyebrow: "Gallery", title: "Real days, real rooms" },
    avail: {
      eyebrow: "Availability", title: "Is your date free?",
      body: "Pick an open date (green) and send an enquiry — this demo isn't wired to a real booking system.",
      open: "Open", hold: "On hold", booked: "Booked",
      unavailable: "That date is unavailable — try another.",
      selected: (d) => `${d} selected — fill in the form below.`,
      cont: "Continue to enquiry",
      months: ["January","February","March","April","May","June","July","August","September","October","November","December"],
      dow: ["Su","Mo","Tu","We","Th","Fr","Sa"],
    },
    form: {
      eyebrow: "Enquire", title: "Send us a question",
      date: "Preferred date", noDate: "None yet (pick one from the calendar above)",
      name: "Couple's name", contact: "Phone / LINE", ceremony: "Ceremony type", guests: "Guest count", msg: "Anything else",
      cOpts: ["Reception banquet","Thai-Chinese ceremony","Morning rite + evening party","Photoshoot / location"],
      submit: "Send enquiry",
      okT: "Thank you! 💌", okB: "We've got your enquiry — the team will reply on LINE / by phone shortly. (This is a demo — nothing was actually sent.)",
      errReq: "This field is required", errGuests: "Guest count must be 1–500",
    },
    map: {
      eyebrow: "Getting here", title: "Easy to find",
      addrT: "Siam Vow Estate", addrL: "Soi Chatuchot 14 · Bangkok  [full address to confirm]",
      dir: "Get directions (Google Maps)", copy: "Copy address", copied: "Copied ✓",
      pins: ["The estate", "Parking", "Chatuchot 14 entrance"],
      chips: ["Don Mueang ~20 min", "Ramintra ~15 min", "Expressway ~10 min"],
    },
    contact: { title: "Contact & booking", phoneL: "Phone", lineL: "LINE", lineSoon: "LINE (coming soon)", call: "Call now", line: "Chat on LINE" },
    footer: { tag: "Wedding estate & film location · Bangkok", rights: "© Siam Vow Estate — demo" },
  },
};

// expose for tests (i18n completeness, reduced-motion assertions)
window.__DICT = DICT;
window.__REDUCED = REDUCED;

/* --------------------------------------------------------- canned data ---- */
// Deterministic availability so the calendar is stable across reloads/tests.
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 }) {
  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(
      (entries) => entries.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} className={"reveal " + (shown ? "is-in " : "") + className}>{children}</Tag>;
}

/* -------------------------------------------------------------- Emblem ---- */
function Emblem({ className }) {
  return (
    <svg className={className} viewBox="0 0 64 64" fill="none" aria-hidden="true">
      <path d="M32 6c4 8 4 13 0 20-4-7-4-12 0-20Z" fill="#C9A24B" />
      <path d="M32 22c7-5 12-5 19 0-6 5-12 6-19 3-7 3-13 2-19-3 7-5 12-5 19 0Z" fill="#A07C2C" />
      <path d="M14 30c6 6 12 8 18 8s12-2 18-8c-2 9-9 16-18 16s-16-7-18-16Z" fill="#D9B254" />
      <circle cx="32" cy="30" r="3.4" fill="#FBF2EF" />
    </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);
  }, []);
  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">Siam Vow Estate</span>
      </a>
      <div className="nav__links">
        <a href="#spaces" onClick={(e) => { e.preventDefault(); goto("spaces"); }}>{t.nav.spaces}</a>
        <a href="#packages" onClick={(e) => { e.preventDefault(); goto("packages"); }}>{t.nav.packages}</a>
        <a href="#gallery" onClick={(e) => { e.preventDefault(); goto("gallery"); }}>{t.nav.gallery}</a>
        <a href="#availability" onClick={(e) => { e.preventDefault(); goto("availability"); }}>{t.nav.availability}</a>
        <a href="#contact" onClick={(e) => { e.preventDefault(); goto("contact"); }}>{t.nav.contact}</a>
      </div>
      <div className="nav__right">
        <button className="langtoggle" onClick={toggleLang} aria-label="Switch language">
          {lang === "th" ? "EN" : "ไทย"}
        </button>
        <button className="btn btn--gold" onClick={() => goto("availability")}>{t.nav.book}</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" alt="Siam Vow Estate banquet hall dressed for a wedding" />
        ) : (
          <video ref={videoRef} autoPlay muted loop playsInline poster="assets/hero-poster.jpg" preload="auto">
            <source src="assets/hero.mp4" type="video/mp4" />
            <source src="assets/hero.webm" type="video/webm" />
          </video>
        )}
      </div>
      <div className="hero__scrim" />
      <div className="hero__inner">
        <Emblem className="hero__emblem" />
        <h1 className="hero__name">SIAM VOW ESTATE</h1>
        <div className="hero__th">{t.hero.th}</div>
        <p className="hero__tag">{t.hero.tag}</p>
        <div className="hero__cta">
          <button className="btn btn--gold" onClick={() => goto("availability")}>{t.hero.cta1}</button>
          <button className="btn btn--ghost" onClick={() => goto("spaces")}>{t.hero.cta2}</button>
        </div>
      </div>
      <div className="hero__scrolldown">{t.hero.scroll}</div>
    </header>
  );
}

/* ----------------------------------------------------------- Signature ---- */
function Signature({ t }) {
  return (
    <section className="section">
      <Reveal className="wrap">
        <p className="eyebrow">{t.sig.eyebrow}</p>
        <h2 className="lead">{t.sig.title}</h2>
        <p className="muted" style={{ maxWidth: 720, marginTop: 16, fontSize: 18 }}>{t.sig.body}</p>
      </Reveal>
    </section>
  );
}

/* --------------------------------------------------------------- Perks ---- */
function Perks({ t }) {
  return (
    <section className="section section--tint">
      <div className="wrap">
        <Reveal>
          <p className="eyebrow">{t.perks.eyebrow}</p>
          <h2 style={{ fontSize: "clamp(28px,4vw,44px)", marginBottom: 28 }}>{t.perks.title}</h2>
        </Reveal>
        <div className="grid grid--3">
          {t.perks.items.map((it, i) => (
            <Reveal key={i} className="card perk">
              <span className="perk__free">{it.free}</span>
              <h3>{it.t}</h3>
              <p className="muted" style={{ margin: 0 }}>{it.d}</p>
            </Reveal>
          ))}
        </div>
        <Reveal><p className="urgency">{t.perks.urgency}</p></Reveal>
      </div>
    </section>
  );
}

/* -------------------------------------------------------------- Spaces ---- */
function Spaces({ t }) {
  return (
    <section className="section" id="spaces">
      <div className="wrap">
        <Reveal>
          <p className="eyebrow">{t.spaces.eyebrow}</p>
          <h2 style={{ fontSize: "clamp(28px,4vw,44px)", marginBottom: 10 }}>{t.spaces.title}</h2>
          <p className="muted" style={{ maxWidth: 640, marginBottom: 28 }}>{t.spaces.body}</p>
        </Reveal>
        <Reveal className="figrow">
          <figure><img src="assets/spaces/hall-raw.jpg" alt={t.spaces.cap1} /><figcaption>{t.spaces.cap1}</figcaption></figure>
          <figure><img src="assets/spaces/ceremony-thai-chinese.jpg" alt={t.spaces.cap2} /><figcaption>{t.spaces.cap2}</figcaption></figure>
        </Reveal>
      </div>
    </section>
  );
}

/* ------------------------------------------------------------ Packages ---- */
function Packages({ t }) {
  return (
    <section className="section section--tint" id="packages">
      <div className="wrap">
        <Reveal>
          <p className="eyebrow">{t.pkg.eyebrow}</p>
          <h2 style={{ fontSize: "clamp(28px,4vw,44px)", marginBottom: 6 }}>{t.pkg.title}</h2>
          <p style={{ marginBottom: 26 }}><span className="confirm">{t.pkg.note}</span></p>
        </Reveal>
        <div className="pkg">
          {t.pkg.tiers.map((tier, i) => (
            <Reveal key={i} className="card">
              <h3 style={{ fontSize: 24 }}>{tier.t}</h3>
              <div className="pkg__price">{tier.p}</div>
              <p className="muted" style={{ flex: 1 }}>{tier.d}</p>
              <button className="btn btn--ghost" style={{ color: "var(--gold-deep)", marginTop: 10 }} onClick={() => goto("availability")}>
                {t.nav.book}
              </button>
            </Reveal>
          ))}
        </div>
      </div>
    </section>
  );
}

/* ------------------------------------------------------------- Gallery ---- */
function Gallery({ t }) {
  const imgs = [
    ["assets/gallery/banquet-wide.jpg", "Dressed banquet, purple uplight"],
    ["assets/gallery/cake-aisle.jpg", "Tiered cake & floral aisle"],
    ["assets/spaces/ceremony-thai-chinese.jpg", "Thai-Chinese ceremony backdrop"],
    ["assets/spaces/hall-raw.jpg", "The transformable hall"],
  ];
  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="gallery">
          {imgs.map(([src, alt], i) => (
            <figure key={i}><img src={src} alt={alt} loading="lazy" /></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; // 6-month window
  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(--gold)" }} />{t.avail.hold}</span>
        <span><i style={{ background: "var(--rose)" }} />{t.avail.booked}</span>
      </div>
    </div>
  );
}

/* -------------------------------------------------------- Availability ---- */
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 style={{}}>
            <div className="card">
              <h3 style={{ fontSize: 24, marginBottom: 10 }}>{t.avail.eyebrow}</h3>
              <p className="muted">{t.sig.body}</p>
              <button className="btn btn--gold" onClick={() => goto("enquire")} style={{ marginTop: 8 }}>{t.avail.cont}</button>
            </div>
          </Reveal>
        </div>
      </div>
    </section>
  );
}

/* ----------------------------------------------------------- Enquiry ------ */
function Enquiry({ t, selected }) {
  const [submitted, setSubmitted] = useState(false);
  const [errors, setErrors] = useState({});
  const formRef = useRef(null);

  const onSubmit = (e) => {
    e.preventDefault();
    const f = e.target;
    const name = f.name.value.trim();
    const contact = f.contact.value.trim();
    const guestsRaw = f.guests.value.trim();
    const guests = Number(guestsRaw);
    const next = {};
    if (!name) next.name = t.form.errReq;
    if (!contact) next.contact = t.form.errReq;
    if (!guestsRaw || !Number.isInteger(guests) || guests < 1 || guests > 500) next.guests = t.form.errGuests;
    setErrors(next);
    if (Object.keys(next).length) {
      // focus the first invalid field by key order (DOM attr not re-rendered yet)
      const firstKey = ["name", "contact", "guests"].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" 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" 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" ref={formRef} 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" inputMode="tel" />
                <div className="field__err">{errors.contact || ""}</div>
              </div>
            </div>
            <div className="two">
              <div className="field">
                <label htmlFor="f-ceremony">{t.form.ceremony}</label>
                <select id="f-ceremony" name="ceremony" defaultValue={t.form.cOpts[0]}>
                  {t.form.cOpts.map((o, i) => <option key={i} value={o}>{o}</option>)}
                </select>
              </div>
              <div className="field" data-invalid={errors.guests ? "true" : undefined}>
                <label htmlFor="f-guests">{t.form.guests}</label>
                <input id="f-guests" name="guests" type="number" min="1" max="500" defaultValue="100" />
                <div className="field__err">{errors.guests || ""}</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--gold btn--block" type="submit">{t.form.submit}</button>
          </form>
        </Reveal>
      </div>
    </section>
  );
}

/* ----------------------------------------------------------------- Map ---- */
function StylizedMap({ t }) {
  // pin positions in % of the canvas
  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 of Siam Vow Estate">
      <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" />
        {/* canal */}
        <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" />
        {/* roads */}
        <path d="M0 50 H100" stroke="#fff" strokeWidth="3.2" />
        <path d="M48 0 V70" stroke="#fff" strokeWidth="2.4" />
        <path d="M0 14 C 30 14, 60 18, 100 12" stroke="#F4D9CE" strokeWidth="1.6" fill="none" />
        <text x="50.5" y="58" fontSize="2.6" fill="#9aa">จตุโชติ 14 · Chatuchot 14</text>
      </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 ? "#A8408A" : "#C9A24B"} />
            <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 = "Siam Vow Estate, Soi Chatuchot 14, Bangkok";
  const copy = async () => {
    try {
      if (navigator.clipboard && navigator.clipboard.writeText) {
        await navigator.clipboard.writeText(address);
      } else throw new Error("no clipboard");
      setCopied(true);
      setTimeout(() => setCopied(false), 2000);
    } catch {
      // fallback: select the address text
      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>{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--gold" 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 ----- */
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>
  );
}

/* ------------------------------------------------------------- Footer ----- */
function Footer({ t }) {
  return (
    <footer className="footer">
      <div className="nav__name" style={{ fontSize: 20, letterSpacing: ".16em" }}>SIAM VOW ESTATE</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>
    </footer>
  );
}

/* --------------------------------------------------------------- App ------ */
function App() {
  const [lang, setLang] = useState(() => {
    try { return localStorage.getItem("sve-lang") || "th"; } catch { return "th"; }
  });
  const [selected, setSelected] = useState(null);
  const t = DICT[lang];

  useEffect(() => {
    document.documentElement.lang = lang;
    try { localStorage.setItem("sve-lang", lang); } catch {}
  }, [lang]);

  // Lenis smooth scroll (skipped under reduced motion)
  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} />
      <Signature t={t} />
      <Perks t={t} />
      <Spaces t={t} />
      <Packages 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 />);
