/* ===========================================================================
   LACQ. Nail Atelier — concept site (Saang Studio)

   One file: config → DICT → data → helpers → components → mount.
   Babel-standalone compiles this in the browser; there is no build step.

   HONESTY CONTRACT (req009): LACQ is a fictional business. That must be stated
   in FOUR places, in BOTH locales — the ribbon under the nav, the <title> and
   og:site_name, the booking success panel, and the footer. Do not remove any of
   them, and do not add reviews, ratings or testimonials: a concept site has no
   customers.

   NO NETWORK (req005): nothing here fetches. The booking wizard is canned state.
   =========================================================================== */

const { useState, useEffect, useRef, useMemo, useCallback } = React;

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

/* ---- config ------------------------------------------------------------- */
const BRAND = "LACQ.";
const BRAND_FULL = "LACQ. Nail Atelier";
const DEFAULT_LANG = "en";
// Placeholders on purpose. A plausible Thai mobile on a public page belongs to
// somebody and WILL ring — see PRD §2.
const PHONE_TEL = "tel:0000000000";
const LINE_ID = null;
const SITE_URL = "https://lacq-nail-concept.vercel.app";
const GA_ID = null;                       // GA4 would break the req005 no-network contract
const OG_LOCALE = { en: "en_US", th: "th_TH" };
const CLOSED_DOW = 1;                     // Monday

/* ---- i18n --------------------------------------------------------------- */
const DICT = {
  en: {
    ribbon: ["Concept site.", "LACQ. is a fictional salon, built by Saang Studio to show what a nail salon's website can do."],
    nav: { work: "Work", menu: "Menu", team: "Artists", visit: "Visit", book: "Book" },
    hero: {
      kicker: "Thonglor · appointment only",
      sub: "Structured gel, chrome and cat-eye, hand-painted art. We don't do fast. We do three weeks with no lift.",
      cta: "Book a set", cta2: "See the work",
      shadeLabel: "Pick a shade",
    },
    work: {
      eyebrow: "The work", title: "Sets we've done.",
      intro: "Filter by what you're after. Every set lists the artist and the price band it falls in.",
      all: "Everything",
    },
    menu: {
      eyebrow: "The menu", title: "What it costs, before you book.",
      intro: "Base sets below. Add-ons stack on top — the ones charged per nail are marked, and the booking form adds them up for you.",
      sets: "Base sets", addons: "Add-ons", perNail: "per nail", mins: "min",
    },
    team: {
      eyebrow: "The artists", title: "Three pairs of hands.",
      intro: "Book the person, not just the slot.",
    },
    book: {
      eyebrow: "Booking", title: "Build your visit.",
      steps: ["Service", "Artist", "When", "You"],
      back: "Back", next: "Continue", submit: "Request this booking",
      s1: "What are you having?", s1addons: "Anything on top?",
      s2: "Who with?", anyArtist: "No preference", anyArtistNote: "We'll give you the first artist free at your time.",
      s3: "Which day?", s3slots: "And what time?", closedNote: "We're closed Mondays.",
      pickDay: "Pick a day to see times.",
      s4: "Where do we reach you?",
      name: "Your name", contact: "Phone or LINE ID", notes: "Anything we should know?",
      notesPh: "Inspo links, an event you're prepping for, a nail you've broken.",
      optional: "optional",
      required: "We need this one.",
      rail: "Your visit", railEmpty: "Pick a service to see the total.",
      duration: "About", from: "from",
      legend: { open: "Free", hold: "Just taken", taken: "Booked" },
      okTitle: "Request received — in the demo.",
      okBody1: "LACQ. Nail Atelier is a fictional salon. Saang Studio built this site to show how booking feels on a real salon's website. Nothing was sent, no data left your browser, and no appointment exists.",
      okBody2: "On a live site this request reaches the salon's LINE and CRM in seconds.",
      okCta: "Want this for your salon?",
      okRecap: "What you picked",
    },
    hygiene: {
      eyebrow: "Hygiene", title: "Tools you can watch us open.",
      items: [
        ["Autoclaved metal", "Every metal tool is washed, bagged and autoclaved between clients. You'll see the pouch opened in front of you."],
        ["Single-use files", "Files, buffers and orange sticks are used once and binned. They are not shared, ever."],
        ["One client per station", "No double-booking a chair. Your station is wiped and reset before you sit down."],
      ],
    },
    care: {
      eyebrow: "Aftercare", title: "Three weeks, then come back.",
      items: [
        ["Weeks 1–3", "A structured set holds three weeks with no lift. Past that, the regrowth starts to catch on things."],
        ["Refill or rebuild", "A refill keeps your length and costs less. A rebuild starts again — worth it every third or fourth visit."],
        ["Don't pick it off", "Picking takes the top layer of your nail with it. Come in for a soak-off; it's ฿250 and ten minutes."],
      ],
    },
    visit: {
      eyebrow: "Visit", title: "Thonglor, Sukhumvit 55.",
      hours: "Hours", hoursBody: "Tue–Sun, 10:00–20:00. Closed Mondays.",
      finding: "Finding us", findingBody: "Second floor, above the coffee roaster. Ring the bell — the door stays shut between appointments.",
      addressNote: "District only — LACQ. is a concept, so there's no real address to send you to.",
      mapNote: "Tap a pin.",
      pins: { salon: "LACQ. (concept — no real address)", bts: "BTS Thong Lo", mall: "Sukhumvit 55 / Thonglor" },
      demoNote: "This is a concept site, so there's nowhere to navigate to. On a real build this button opens Google Maps.",
      directions: "Directions",
      faqTitle: "Before you book",
      faq: [
        ["Do you take walk-ins?", "No — appointment only. It's how we keep a chair free for the time you booked instead of running an hour behind."],
        ["I've got another salon's gel on. Is that a problem?", "Not at all. Soak-off for work that isn't ours is ฿400 — it takes longer and is gentler on the nail than picking at it."],
        ["How long does a full set take?", "A gel set is about 90 minutes. Structured or extensions with art can run past two and a half hours; the booking form gives you an estimate as you pick."],
        ["Is this a real salon?", "No. LACQ. is a concept built by Saang Studio to show what a nail salon's website can do. Nothing here can be booked."],
      ],
    },
    footer: {
      by: "A concept site by", byLink: "Saang Studio",
      disclosure: "LACQ. Nail Atelier is a fictional business. No such salon exists, nothing on this page can be booked, and no prices here are any real salon's rates.",
      want: "Want one of these for your salon?",
      wantCta: "saangstudio.com/nail-salons",
    },
    sticky: { call: "Call", book: "Book" },
    money: (n) => "฿" + n.toLocaleString("en-US"),
  },
  th: {
    ribbon: ["เว็บตัวอย่าง", "LACQ. เป็นร้านสมมติ Saang Studio ทำขึ้นเพื่อให้เห็นว่าเว็บไซต์ร้านทำเล็บทำอะไรได้บ้าง"],
    nav: { work: "ผลงาน", menu: "ราคา", team: "ช่าง", visit: "การเดินทาง", book: "จอง" },
    hero: {
      kicker: "ทองหล่อ · จองล่วงหน้าเท่านั้น",
      sub: "เล็บเจลต่อโครง โครม แคทอาย และเพนต์มือ เราไม่เน้นเร็ว แต่เน้นอยู่ได้สามสัปดาห์แบบไม่ล่อน",
      cta: "จองคิว", cta2: "ดูผลงาน",
      shadeLabel: "เลือกสี",
    },
    work: {
      eyebrow: "ผลงาน", title: "เซ็ตที่เราทำ",
      intro: "กรองดูตามแบบที่อยากได้ ทุกเซ็ตบอกชื่อช่างและช่วงราคาไว้ให้",
      all: "ทั้งหมด",
    },
    menu: {
      eyebrow: "ราคา", title: "รู้ราคาก่อนจอง",
      intro: "ด้านล่างคือเซ็ตพื้นฐาน ออปชันบวกเพิ่มได้ อันไหนคิดรายเล็บเราใส่กำกับไว้ และระบบจองจะบวกให้อัตโนมัติ",
      sets: "เซ็ตพื้นฐาน", addons: "ออปชันเสริม", perNail: "ต่อเล็บ", mins: "นาที",
    },
    team: {
      eyebrow: "ช่างของเรา", title: "สามคู่มือ",
      intro: "เลือกช่างได้ ไม่ใช่แค่เลือกเวลา",
    },
    book: {
      eyebrow: "จองคิว", title: "จัดคิวของคุณ",
      steps: ["บริการ", "ช่าง", "วันเวลา", "ข้อมูล"],
      back: "ย้อนกลับ", next: "ถัดไป", submit: "ส่งคำขอจอง",
      s1: "จะทำอะไรดี", s1addons: "เพิ่มอะไรอีกไหม",
      s2: "อยากทำกับช่างคนไหน", anyArtist: "ช่างคนไหนก็ได้", anyArtistNote: "เราจะจัดช่างที่ว่างในเวลานั้นให้",
      s3: "วันไหนดี", s3slots: "แล้วกี่โมง", closedNote: "วันจันทร์ร้านปิด",
      pickDay: "เลือกวันก่อน แล้วจะเห็นเวลาที่ว่าง",
      s4: "ติดต่อคุณยังไง",
      name: "ชื่อคุณ", contact: "เบอร์โทร หรือ LINE ID", notes: "มีอะไรอยากบอกช่างไหม",
      notesPh: "ลิงก์แบบที่ชอบ งานที่ต้องไป หรือเล็บที่หัก",
      optional: "ไม่บังคับ",
      required: "กรอกช่องนี้ด้วยนะ",
      rail: "คิวของคุณ", railEmpty: "เลือกบริการเพื่อดูราคารวม",
      duration: "ประมาณ", from: "เริ่มต้น",
      legend: { open: "ว่าง", hold: "เพิ่งถูกจอง", taken: "เต็ม" },
      okTitle: "รับคำขอแล้ว — ในเดโม",
      okBody1: "LACQ. Nail Atelier เป็นร้านสมมติ Saang Studio สร้างเว็บไซต์นี้ขึ้นเพื่อแสดงให้เห็นว่าระบบจองบนเว็บไซต์ร้านจริงทำงานอย่างไร ไม่มีข้อมูลใดถูกส่งออกจากเบราว์เซอร์ของคุณ และไม่มีการจองเกิดขึ้นจริง",
      okBody2: "บนเว็บไซต์จริง คำขอนี้จะเข้าไปที่ LINE และระบบ CRM ของร้านภายในไม่กี่วินาที",
      okCta: "อยากได้แบบนี้สำหรับร้านคุณไหม",
      okRecap: "สิ่งที่คุณเลือก",
    },
    hygiene: {
      eyebrow: "ความสะอาด", title: "อุปกรณ์ที่เปิดให้ดูตรงหน้า",
      items: [
        ["นึ่งฆ่าเชื้อทุกชิ้น", "อุปกรณ์โลหะทุกชิ้นล้าง แพ็ก แล้วเข้าหม้อนึ่งฆ่าเชื้อก่อนใช้กับลูกค้าคนต่อไป เราแกะซองให้ดูตรงหน้า"],
        ["ตะไบใช้ครั้งเดียว", "ตะไบ บัฟเฟอร์ และไม้ดันจมูกเล็บ ใช้ครั้งเดียวแล้วทิ้ง ไม่มีการใช้ซ้ำ"],
        ["หนึ่งลูกค้าต่อหนึ่งโต๊ะ", "ไม่จองซ้อนโต๊ะ เช็ดและจัดโต๊ะใหม่ทุกครั้งก่อนคุณนั่ง"],
      ],
    },
    care: {
      eyebrow: "การดูแล", title: "สามสัปดาห์ แล้วกลับมาเจอกัน",
      items: [
        ["สัปดาห์ที่ 1–3", "เซ็ตต่อโครงอยู่ได้สามสัปดาห์แบบไม่ล่อน หลังจากนั้นเล็บที่งอกจะเริ่มไปเกี่ยวของ"],
        ["เติมหรือรื้อใหม่", "เติมจะคงความยาวเดิมและถูกกว่า ส่วนรื้อทำใหม่คุ้มทุกๆ ครั้งที่สามหรือสี่"],
        ["อย่าแกะเอง", "แกะทีเดียวหน้าเล็บหลุดตามไปด้วย เข้ามาให้เราถอดให้ดีกว่า ค่าถอด ฿250 ใช้เวลาสิบนาที"],
      ],
    },
    visit: {
      eyebrow: "การเดินทาง", title: "ทองหล่อ สุขุมวิท 55",
      hours: "เวลาเปิด", hoursBody: "อังคาร–อาทิตย์ 10:00–20:00 ปิดวันจันทร์",
      finding: "หาร้านยังไง", findingBody: "ชั้นสอง เหนือร้านคั่วกาแฟ กดกริ่งเรียกได้เลย ประตูปิดไว้ระหว่างคิว",
      addressNote: "บอกแค่ย่าน เพราะ LACQ. เป็นร้านสมมติ ไม่มีที่อยู่จริงให้ไป",
      mapNote: "กดที่หมุด",
      pins: { salon: "LACQ. (ร้านสมมติ ไม่มีที่อยู่จริง)", bts: "BTS ทองหล่อ", mall: "สุขุมวิท 55 / ทองหล่อ" },
      demoNote: "นี่คือเว็บตัวอย่าง จึงไม่มีที่ให้นำทางไป ถ้าเป็นเว็บจริง ปุ่มนี้จะเปิด Google Maps",
      directions: "เส้นทาง",
      faqTitle: "ก่อนจอง",
      faq: [
        ["Walk-in ได้ไหม", "ไม่ได้ รับเฉพาะจองล่วงหน้า เพราะอยากกันเวลาไว้ให้คิวที่คุณจองจริงๆ ไม่ใช่ให้รอเป็นชั่วโมง"],
        ["ติดเจลจากร้านอื่นมา ทำได้ไหม", "ได้เลย ค่าถอดงานจากร้านอื่นอยู่ที่ ฿400 ใช้เวลานานกว่าและถนอมหน้าเล็บมากกว่าการแกะเอง"],
        ["ทำเซ็ตนึงใช้เวลานานแค่ไหน", "เซ็ตเจลประมาณ 90 นาที ถ้าต่อโครงหรือต่อเล็บพร้อมเพนต์อาจเกินสองชั่วโมงครึ่ง ระบบจองจะประเมินเวลาให้ตอนคุณเลือก"],
        ["ร้านนี้มีอยู่จริงไหม", "ไม่มี LACQ. เป็นร้านสมมติที่ Saang Studio ทำขึ้นเพื่อให้เห็นว่าเว็บไซต์ร้านทำเล็บทำอะไรได้บ้าง จองจริงไม่ได้"],
      ],
    },
    footer: {
      by: "เว็บตัวอย่างโดย", byLink: "Saang Studio",
      disclosure: "LACQ. Nail Atelier เป็นร้านสมมติ ไม่มีร้านนี้อยู่จริง จองอะไรบนหน้านี้ไม่ได้ และราคาทั้งหมดไม่ใช่ราคาของร้านใดร้านหนึ่ง",
      want: "อยากได้แบบนี้สำหรับร้านคุณไหม",
      wantCta: "saangstudio.com/nail-salons",
    },
    sticky: { call: "โทร", book: "จอง" },
    money: (n) => "฿" + n.toLocaleString("en-US"),
  },
};

/* ---- data — the ONLY source of prices (req010 asserts this) -------------- */
const SHADES = [
  { id: "s1", hex: "var(--lacq-1)", en: "Bare Milk",      th: "แบร์ มิลค์" },
  { id: "s2", hex: "var(--lacq-2)", en: "Siam Red",       th: "สยาม เรด" },
  { id: "s3", hex: "var(--lacq-3)", en: "Emerald Chrome", th: "เอเมอรัลด์ โครม" },
  { id: "s4", hex: "var(--lacq-4)", en: "Pearl Chrome",   th: "เพิร์ล โครม" },
  { id: "s5", hex: "var(--lacq-5)", en: "Midnight Jelly", th: "มิดไนต์ เจลลี่" },
  { id: "s6", hex: "var(--lacq-6)", en: "Latte",          th: "ลาเต้" },
];

// Every price sits inside a documented Bangkok market band — see PRD.md §3.
// Cite the band, never a salon. Do not edit a number here without updating that
// table and its sources.
const SERVICES = [
  { id: "classic",    thb: 750,  min: 45,  en: ["Classic manicure", "Shape, cuticles, buff and a coat of polish."], th: ["ทำเล็บพื้นฐาน", "ตัดแต่งทรง ดันจมูกเล็บ ขัด แล้วทาสี"] },
  { id: "gel",        thb: 1200, min: 90,  en: ["Gel set", "Gel colour on your own nail. Two to three weeks."],     th: ["เซ็ตเจล", "ทาเจลบนเล็บจริง อยู่ได้สองถึงสามสัปดาห์"] },
  { id: "structured", thb: 1800, min: 120, en: ["Structured gel", "Builder gel over your own nail. Three weeks, no lift."], th: ["เจลต่อโครง", "เสริมโครงบนเล็บจริง สามสัปดาห์ ไม่ล่อน"] },
  { id: "extensions", thb: 2400, min: 150, en: ["Soft-gel extensions", "Added length, shaped to your hand."],       th: ["ต่อเล็บ soft gel", "ต่อความยาว จัดทรงให้เข้ากับมือ"] },
  { id: "pedi",       thb: 1400, min: 75,  en: ["Gel pedicure", "Feet, properly. Includes a soak and a scrub."],    th: ["เพดิเคียวร์เจล", "ดูแลเท้าเต็มรูปแบบ รวมแช่และขัด"] },
];

const ADDONS = [
  { id: "soakoff_ours",  thb: 250, min: 15, perNail: false, en: ["Soak-off — our work", "Ten minutes, no picking."],              th: ["ถอดเจล — งานของเรา", "สิบนาที ไม่ต้องแกะ"] },
  { id: "soakoff_other", thb: 400, min: 30, perNail: false, en: ["Soak-off — another salon's", "Takes longer and we go gently."], th: ["ถอดเจล — งานจากร้านอื่น", "ใช้เวลานานกว่า และเราค่อยๆ ทำ"] },
  { id: "chrome",        thb: 400, min: 20, perNail: false, en: ["Chrome or cat-eye", "Across the full set."],                    th: ["โครม หรือ แคทอาย", "ทำทั้งเซ็ต"] },
  { id: "french",        thb: 350, min: 20, perNail: false, en: ["French", "Classic or a coloured tip."],                          th: ["เฟรนช์", "แบบคลาสสิก หรือปลายสี"] },
  { id: "art",           thb: 150, min: 12, perNail: true,  en: ["Hand-painted art", "Priced per nail — pick how many."],          th: ["เพนต์มือ", "คิดรายเล็บ เลือกจำนวนได้"] },
  { id: "repair",        thb: 150, min: 10, perNail: true,  en: ["Repair", "A broken nail rebuilt. Per nail."],                    th: ["ซ่อมเล็บ", "เสริมเล็บที่หัก คิดรายเล็บ"] },
  { id: "cuticle",       thb: 400, min: 20, perNail: false, en: ["Cuticle treatment", "Oil, massage and paraffin."],               th: ["ทรีตเมนต์จมูกเล็บ", "น้ำมัน นวด และพาราฟิน"] },
];

const ARTISTS = [
  { id: "ploy", initials: "P",  en: ["Ploy", "Chrome & cat-eye", "Seven years. Mirrors, shifting pigments, anything that catches light."], th: ["พลอย", "โครม & แคทอาย", "เจ็ดปี ถนัดงานเงา ผงเปลี่ยนสี และอะไรก็ตามที่สะท้อนแสง"] },
  { id: "mint", initials: "M",  en: ["Mint", "3D & hand-paint", "Fine brushwork. Bring her a reference and she'll better it."],           th: ["มิ้นท์", "งาน 3D & เพนต์มือ", "ฝีแปรงละเอียด เอารูปตัวอย่างมา เดี๋ยวเธอทำให้ดีกว่าเดิม"] },
  { id: "fai",  initials: "F",  en: ["Fai", "Structure & extensions", "Apex and balance. If your sets keep lifting, book Fai."],          th: ["ฝ้าย", "งานโครง & ต่อเล็บ", "เก็บทรงและสมดุลเป๊ะ ถ้าเล็บล่อนบ่อย จองฝ้าย"] },
];

const GALLERY = [
  { id: "g1", cat: "structured", artist: "fai",  band: "structured", en: "Structured almond, milk base",  th: "ต่อโครงทรงอัลมอนด์ สีน้ำนม" },
  { id: "g2", cat: "chrome",     artist: "ploy", band: "gel",        en: "Pearl chrome, short square",    th: "เพิร์ลโครม ทรงสั้นเหลี่ยม" },
  { id: "g3", cat: "art",        artist: "mint", band: "gel",        en: "Hand-painted florals, six nails", th: "เพนต์ลายดอกไม้ หกเล็บ" },
  { id: "g4", cat: "french",     artist: "ploy", band: "gel",        en: "Micro-French, natural tip",     th: "ไมโครเฟรนช์ ปลายธรรมชาติ" },
  { id: "g5", cat: "extensions", artist: "fai",  band: "extensions", en: "Soft-gel extensions, long oval", th: "ต่อเล็บ soft gel ทรงรีสวย" },
  { id: "g6", cat: "chrome",     artist: "ploy", band: "structured", en: "Emerald cat-eye over structure", th: "แคทอายเขียวมรกตบนโครง" },
  { id: "g7", cat: "art",        artist: "mint", band: "structured", en: "3D pearls, accent nails",       th: "มุก 3 มิติ เล็บเน้น" },
  { id: "g8", cat: "structured", artist: "fai",  band: "structured", en: "Latte structured, squoval",     th: "ต่อโครงสีลาเต้ ทรงสควอวัล" },
];

const CATS = [
  { id: "structured", en: "Structured gel",   th: "ต่อโครง" },
  { id: "french",     en: "French",           th: "เฟรนช์" },
  { id: "chrome",     en: "Chrome & cat-eye", th: "โครม & แคทอาย" },
  { id: "art",        en: "Nail art",         th: "เพนต์มือ" },
  { id: "extensions", en: "Extensions",       th: "ต่อเล็บ" },
];

// Exposed so req010 can recompute every displayed total from the source data
// and prove nothing is hard-coded in the markup.
window.__DATA = { SERVICES, ADDONS, ARTISTS, GALLERY, CATS, SHADES, CLOSED_DOW };

/* ---- helpers ------------------------------------------------------------ */
function initialLang() {
  const q = new URLSearchParams(location.search).get("lang");
  if (q === "en" || q === "th") return q;
  try { const s = localStorage.getItem("lacq_lang"); if (s === "en" || s === "th") return s; } catch (e) {}
  return DEFAULT_LANG;
}

// Keeps the static <head> honest when the visitor toggles locale.
function enhanceSeo(lang) {
  document.documentElement.lang = lang;
  const set = (sel, attr, val) => { const el = document.querySelector(sel); if (el) el.setAttribute(attr, val); };
  const url = lang === DEFAULT_LANG ? SITE_URL + "/" : SITE_URL + "/?lang=" + lang;
  set('link[rel="canonical"]', "href", url);
  set('meta[property="og:url"]', "content", url);
  set('meta[property="og:locale"]', "content", OG_LOCALE[lang]);
}

/**
 * Canned availability. PURE: the same (dateIso, artistId, slotIndex) always
 * gives the same state, forever. Deliberately no Date.now() inside — otherwise
 * the grid would drift while a prospect is looking at it, and req012 would flake.
 */
function slotState(dateIso, artistId, i) {
  const seed = [...(dateIso + "|" + artistId)].reduce((a, c) => (a * 31 + c.charCodeAt(0)) >>> 0, 7);
  const v = ((seed + i * 2654435761) >>> 0) % 100;
  return v < 34 ? "taken" : v < 42 ? "hold" : "open";
}

const SLOT_START = 10 * 60;         // 10:00
const SLOT_END = 20 * 60;           // 20:00
const SLOT_STEP = 30;
const SLOTS = (() => { const out = []; for (let m = SLOT_START; m < SLOT_END; m += SLOT_STEP) out.push(m); return out; })();
const hhmm = (m) => String(Math.floor(m / 60)).padStart(2, "0") + ":" + String(m % 60).padStart(2, "0");
const iso = (d) => d.getFullYear() + "-" + String(d.getMonth() + 1).padStart(2, "0") + "-" + String(d.getDate()).padStart(2, "0");

function nextDays(n) {
  const out = []; const base = new Date(); base.setHours(0, 0, 0, 0);
  for (let i = 0; i < n; i++) { const d = new Date(base); d.setDate(base.getDate() + i); out.push(d); }
  return out;
}

const svcById = (id) => SERVICES.find((s) => s.id === id);
const addById = (id) => ADDONS.find((a) => a.id === id);

function totalTHB(serviceId, addonIds, nailCount) {
  const s = svcById(serviceId); if (!s) return 0;
  return s.thb + addonIds.reduce((sum, id) => { const a = addById(id); return sum + a.thb * (a.perNail ? nailCount : 1); }, 0);
}
function totalMin(serviceId, addonIds, nailCount) {
  const s = svcById(serviceId); if (!s) return 0;
  return s.min + addonIds.reduce((sum, id) => { const a = addById(id); return sum + a.min * (a.perNail ? nailCount : 1); }, 0);
}
const durationText = (min) => { const h = Math.floor(min / 60), m = min % 60; return (h ? h + " h " : "") + (m ? m + " m" : "").trim(); };

function goto(id) { const el = document.getElementById(id); if (el) el.scrollIntoView({ behavior: REDUCED ? "auto" : "smooth", block: "start" }); }

/* ---- placeholder art ----------------------------------------------------
   Ships until real imagery lands (see assets/PROVENANCE.md for the prompts).
   Deliberately typographic rather than a stock photo of someone else's hands. */
function Placeholder({ label, seedIndex = 0 }) {
  const a = SHADES[seedIndex % SHADES.length].hex;
  const b = SHADES[(seedIndex + 2) % SHADES.length].hex;
  return (
    <svg viewBox="0 0 400 400" role="img" aria-label={label} preserveAspectRatio="xMidYMid slice">
      <rect width="400" height="400" fill="var(--ink-2)" />
      <circle cx="300" cy="110" r="150" fill={a} opacity="0.30" />
      <circle cx="110" cy="300" r="120" fill={b} opacity="0.24" />
      <text x="28" y="368" fill="var(--on-dark)" opacity="0.85"
            style={{ font: "600 19px var(--display)", letterSpacing: ".16em" }}>LACQ.</text>
    </svg>
  );
}

function Reveal({ children, className = "", tag: Tag = "div", id }) {
  const ref = useRef(null);
  const [seen, setSeen] = useState(REDUCED);
  useEffect(() => {
    if (REDUCED || !ref.current) return;
    const io = new IntersectionObserver((es) => { es.forEach((e) => { if (e.isIntersecting) { setSeen(true); io.disconnect(); } }); }, { rootMargin: "0px 0px -8% 0px" });
    io.observe(ref.current);
    const failsafe = setTimeout(() => setSeen(true), 2500);   // never leave content hidden
    return () => { io.disconnect(); clearTimeout(failsafe); };
  }, []);
  return <Tag id={id} ref={ref} className={"reveal " + (seen ? "is-in " : "") + className}>{children}</Tag>;
}

/* ---- sections ----------------------------------------------------------- */

// req009: disclosure #1 of 4. Above the fold at every width, both locales.
function Ribbon({ t }) {
  return (
    <div className="ribbon" data-concept-ribbon>
      <strong>{t.ribbon[0]}</strong> {t.ribbon[1]}
    </div>
  );
}

function Nav({ t, lang, toggleLang }) {
  return (
    <nav className="nav">
      <a className="nav__name" href="#top" onClick={(e) => { e.preventDefault(); goto("top"); }}>{BRAND}</a>
      <div className="nav__links">
        <a href="#work" onClick={(e) => { e.preventDefault(); goto("work"); }}>{t.nav.work}</a>
        <a href="#menu" onClick={(e) => { e.preventDefault(); goto("menu"); }}>{t.nav.menu}</a>
        <a href="#team" onClick={(e) => { e.preventDefault(); goto("team"); }}>{t.nav.team}</a>
        <a href="#visit" onClick={(e) => { e.preventDefault(); goto("visit"); }}>{t.nav.visit}</a>
      </div>
      <div className="nav__right">
        <button className="langtoggle" onClick={toggleLang} aria-label="Switch language">
          {lang === "en" ? "ไทย" : "EN"}
        </button>
        <button className="btn btn--accent" onClick={() => goto("book")}>{t.nav.book}</button>
      </div>
    </nav>
  );
}

function Hero({ t, lang, shade, setShade }) {
  return (
    <header className="hero" id="top">
      <div className="wrap hero__grid">
        <div>
          <p className="eyebrow">{t.hero.kicker}</p>
          <h1 className="hero__name">{BRAND_FULL}</h1>
          <p className="hero__sub">{t.hero.sub}</p>
          <div className="hero__cta">
            <button className="btn btn--accent" onClick={() => goto("book")}>{t.hero.cta}</button>
            <button className="btn btn--ghost" onClick={() => goto("work")}>{t.hero.cta2}</button>
          </div>
          <div className="shades" role="group" aria-label={t.hero.shadeLabel}>
            {SHADES.map((s) => (
              <button key={s.id} className="shade" style={{ background: s.hex }}
                      aria-pressed={shade.id === s.id} aria-label={s[lang]}
                      onClick={() => setShade(s)} />
            ))}
            <span className="shade__label">{shade[lang]}</span>
          </div>
        </div>
        <div className="hero__media" style={{ borderTop: "4px solid " + shade.hex }}>
          <Placeholder label={BRAND_FULL} seedIndex={SHADES.indexOf(shade)} />
        </div>
      </div>
    </header>
  );
}

function Work({ t, lang }) {
  const [cat, setCat] = useState("all");
  const shown = cat === "all" ? GALLERY : GALLERY.filter((g) => g.cat === cat);
  return (
    <section className="section" id="work">
      <div className="wrap">
        <Reveal>
          <p className="eyebrow">{t.work.eyebrow}</p>
          <h2>{t.work.title}</h2>
          <p className="lead muted">{t.work.intro}</p>
        </Reveal>
        <div className="chips" role="group" aria-label={t.work.eyebrow}>
          <button className="chip" aria-pressed={cat === "all"} onClick={() => setCat("all")}>{t.work.all}</button>
          {CATS.map((c) => (
            <button key={c.id} className="chip" aria-pressed={cat === c.id} onClick={() => setCat(c.id)}>{c[lang]}</button>
          ))}
        </div>
        <div className="gallery" data-gallery>
          {shown.map((g, i) => {
            const artist = ARTISTS.find((a) => a.id === g.artist);
            const band = svcById(g.band);
            return (
              <figure className="tile" key={g.id} data-tile={g.cat}>
                <Placeholder label={g[lang]} seedIndex={i} />
                <figcaption className="tile__cap">
                  {g[lang]} · {artist[lang][0]} · {t.book.from} {t.money(band.thb)}
                </figcaption>
              </figure>
            );
          })}
        </div>
      </div>
    </section>
  );
}

function Menu({ t, lang }) {
  return (
    <section className="section section--dark" id="menu">
      <div className="wrap">
        <Reveal>
          <p className="eyebrow">{t.menu.eyebrow}</p>
          <h2>{t.menu.title}</h2>
          <p className="lead muted">{t.menu.intro}</p>
        </Reveal>
        <div className="grid" style={{ display: "grid", gridTemplateColumns: "1fr", gap: 40, marginTop: 36 }}>
          <div>
            <p className="eyebrow">{t.menu.sets}</p>
            <div className="prices">
              {SERVICES.map((s) => (
                <div className="price-row" key={s.id} data-price-row={s.id}>
                  <div>
                    <div className="price-row__name">{s[lang][0]}</div>
                    <div className="price-row__meta">{s[lang][1]}</div>
                  </div>
                  <div className="price-row__meta">{s.min} {t.menu.mins}</div>
                  <div className="price-row__thb">{t.money(s.thb)}</div>
                </div>
              ))}
            </div>
          </div>
          <div>
            <p className="eyebrow">{t.menu.addons}</p>
            <div className="prices">
              {ADDONS.map((a) => (
                <div className="price-row" key={a.id} data-price-row={a.id}>
                  <div>
                    <div className="price-row__name">{a[lang][0]}</div>
                    <div className="price-row__meta">{a[lang][1]}</div>
                  </div>
                  <div className="price-row__meta">{a.perNail ? t.menu.perNail : a.min + " " + t.menu.mins}</div>
                  <div className="price-row__thb">{t.money(a.thb)}</div>
                </div>
              ))}
            </div>
          </div>
        </div>
      </div>
    </section>
  );
}

function Team({ t, lang }) {
  return (
    <section className="section" id="team">
      <div className="wrap">
        <Reveal>
          <p className="eyebrow">{t.team.eyebrow}</p>
          <h2>{t.team.title}</h2>
          <p className="lead muted">{t.team.intro}</p>
        </Reveal>
        <div className="roster" style={{ marginTop: 34 }}>
          {ARTISTS.map((a, i) => (
            <Reveal key={a.id}>
              {/* Face-cropped by design — an invented name must never sit under a
                  recognisable real face. See PRD §5. */}
              <div className="artist__media"><Placeholder label={a[lang][0]} seedIndex={i + 1} /></div>
              <div className="artist__mono">{a.initials}</div>
              <h3>{a[lang][0]}</h3>
              <p className="price-row__meta" style={{ marginBottom: 6 }}>{a[lang][1]}</p>
              <p className="muted">{a[lang][2]}</p>
            </Reveal>
          ))}
        </div>
      </div>
    </section>
  );
}

function Band({ t, id, block, dark }) {
  return (
    <section className={"section" + (dark ? " section--dark" : "")} id={id}>
      <div className="wrap">
        <Reveal>
          <p className="eyebrow">{block.eyebrow}</p>
          <h2>{block.title}</h2>
        </Reveal>
        <div className="grid grid--3" style={{ marginTop: 30 }}>
          {block.items.map(([h, b]) => (
            <Reveal className="card" key={h}>
              <h3 style={{ fontSize: 20, marginBottom: 8 }}>{h}</h3>
              <p className="muted" style={{ margin: 0 }}>{b}</p>
            </Reveal>
          ))}
        </div>
      </div>
    </section>
  );
}

/* ---- booking wizard: canned, posts nowhere (req005) --------------------- */
function SummaryRail({ t, lang, state }) {
  const s = svcById(state.serviceId);
  if (!s) return <aside className="rail"><h3>{t.book.rail}</h3><p className="rail__empty">{t.book.railEmpty}</p></aside>;
  const thb = totalTHB(state.serviceId, state.addonIds, state.nails);
  const min = totalMin(state.serviceId, state.addonIds, state.nails);
  const artist = state.artistId ? ARTISTS.find((a) => a.id === state.artistId) : null;
  return (
    <aside className="rail" data-rail>
      <h3>{t.book.rail}</h3>
      <div className="rail__line"><span>{s[lang][0]}</span><span>{t.money(s.thb)}</span></div>
      {state.addonIds.map((id) => {
        const a = addById(id); const n = a.perNail ? state.nails : 1;
        return (
          <div className="rail__line" key={id}>
            <span>+ {a[lang][0]}{a.perNail ? " ×" + n : ""}</span>
            <span>{t.money(a.thb * n)}</span>
          </div>
        );
      })}
      <hr className="rail__rule" />
      <div className="rail__line"><span>{t.book.duration}</span><span>{durationText(min)}</span></div>
      <div className="rail__line"><span>{t.book.from}</span><span className="rail__total" data-rail-total>{t.money(thb)}</span></div>
      {artist && <div className="rail__line"><span>{artist[lang][0]}</span><span /></div>}
      {state.date && <div className="rail__line"><span>{state.date}</span><span>{state.slot != null ? hhmm(state.slot) : ""}</span></div>}
    </aside>
  );
}

function Book({ t, lang }) {
  const [step, setStep] = useState(0);
  const [done, setDone] = useState(false);
  const [err, setErr] = useState({});
  const [state, setState] = useState({ serviceId: "", addonIds: [], nails: 2, artistId: "", date: "", slot: null, name: "", contact: "", notes: "" });
  const days = useMemo(() => nextDays(14), []);
  const set = (patch) => setState((s) => ({ ...s, ...patch }));
  const toggleAddon = (id) => set({ addonIds: state.addonIds.includes(id) ? state.addonIds.filter((x) => x !== id) : [...state.addonIds, id] });
  const perNailPicked = state.addonIds.some((id) => addById(id).perNail);

  const canNext = [
    () => !!state.serviceId,
    () => state.artistId !== "",
    () => !!state.date && state.slot != null,
    () => true,
  ][step];

  function submit(e) {
    e.preventDefault();
    // Honeypot: real visitors never see this field.
    if (e.target.company_url && e.target.company_url.value) { setDone(true); return; }
    const next = {};
    if (!state.name.trim()) next.name = true;
    if (!state.contact.trim()) next.contact = true;
    setErr(next);
    if (Object.keys(next).length) return;
    setDone(true);   // canned. Nothing is sent anywhere — see req005.
  }

  if (done) {
    const s = svcById(state.serviceId);
    const artist = state.artistId ? ARTISTS.find((a) => a.id === state.artistId) : null;
    return (
      <section className="section" id="book">
        <div className="wrap">
          <div className="book__panel done" data-booking-done>
            <div className="done__mark" aria-hidden="true">✓</div>
            {/* req009: disclosure #3 of 4. */}
            <h2>{t.book.okTitle}</h2>
            <p className="muted">{t.book.okBody1}</p>
            <p className="muted">{t.book.okBody2}</p>
            <div className="done__recap">
              <p className="eyebrow" style={{ margin: "0 0 8px" }}>{t.book.okRecap}</p>
              <div className="rail__line"><span>{s[lang][0]}</span><span>{t.money(s.thb)}</span></div>
              {state.addonIds.map((id) => {
                const a = addById(id); const n = a.perNail ? state.nails : 1;
                return <div className="rail__line" key={id}><span>+ {a[lang][0]}{a.perNail ? " ×" + n : ""}</span><span>{t.money(a.thb * n)}</span></div>;
              })}
              <hr className="rail__rule" style={{ borderColor: "var(--line)" }} />
              <div className="rail__line"><span>{artist ? artist[lang][0] : t.book.anyArtist}</span><span>{state.date} {state.slot != null ? hhmm(state.slot) : ""}</span></div>
              <div className="rail__line"><span>{t.book.duration} {durationText(totalMin(state.serviceId, state.addonIds, state.nails))}</span>
                <strong>{t.money(totalTHB(state.serviceId, state.addonIds, state.nails))}</strong></div>
            </div>
            <p style={{ marginTop: 20 }}>
              <a className="btn btn--accent" href="https://saangstudio.com/en/nail-salons">{t.book.okCta}</a>
            </p>
          </div>
        </div>
      </section>
    );
  }

  return (
    <section className="section" id="book">
      <div className="wrap">
        <Reveal>
          <p className="eyebrow">{t.book.eyebrow}</p>
          <h2>{t.book.title}</h2>
        </Reveal>
        <div className="book" style={{ marginTop: 28 }}>
          <form className="book__panel" onSubmit={submit} noValidate>
            <ol className="book__steps">
              {t.book.steps.map((label, i) => (
                <li className="book__step" key={label} aria-current={i === step ? "step" : undefined}>
                  {i + 1}. {label}{i < 3 ? " ·" : ""}
                </li>
              ))}
            </ol>

            {step === 0 && (
              <div data-step="1">
                <h3 style={{ fontSize: 20, marginBottom: 12 }}>{t.book.s1}</h3>
                {SERVICES.map((s) => (
                  <label className="opt" key={s.id}>
                    <input type="radio" name="service" value={s.id} checked={state.serviceId === s.id}
                           onChange={() => set({ serviceId: s.id })} />
                    <span className="opt__body">
                      <span className="opt__name">{s[lang][0]}</span>
                      <span className="opt__meta"> · {s.min} {t.menu.mins}</span>
                      <div className="opt__meta">{s[lang][1]}</div>
                    </span>
                    <span className="opt__thb">{t.money(s.thb)}</span>
                  </label>
                ))}
                <h3 style={{ fontSize: 20, margin: "22px 0 12px" }}>{t.book.s1addons}</h3>
                {ADDONS.map((a) => (
                  <label className="opt" key={a.id}>
                    <input type="checkbox" name="addon" value={a.id} checked={state.addonIds.includes(a.id)}
                           onChange={() => toggleAddon(a.id)} />
                    <span className="opt__body">
                      <span className="opt__name">{a[lang][0]}</span>
                      {a.perNail && <span className="opt__meta"> · {t.menu.perNail}</span>}
                      <div className="opt__meta">{a[lang][1]}</div>
                    </span>
                    <span className="opt__thb">{t.money(a.thb)}</span>
                  </label>
                ))}
                {perNailPicked && (
                  <div className="stepper" data-nail-stepper>
                    <button type="button" aria-label="minus" onClick={() => set({ nails: Math.max(1, state.nails - 1) })}>−</button>
                    <output aria-live="polite">{state.nails}</output>
                    <button type="button" aria-label="plus" onClick={() => set({ nails: Math.min(10, state.nails + 1) })}>+</button>
                    <span className="opt__meta">{t.menu.perNail}</span>
                  </div>
                )}
              </div>
            )}

            {step === 1 && (
              <div data-step="2">
                <h3 style={{ fontSize: 20, marginBottom: 12 }}>{t.book.s2}</h3>
                {ARTISTS.map((a) => (
                  <label className="opt" key={a.id}>
                    <input type="radio" name="artist" value={a.id} checked={state.artistId === a.id} onChange={() => set({ artistId: a.id })} />
                    <span className="opt__body">
                      <span className="opt__name">{a[lang][0]}</span>
                      <div className="opt__meta">{a[lang][1]}</div>
                    </span>
                  </label>
                ))}
                <label className="opt">
                  <input type="radio" name="artist" value="any" checked={state.artistId === "any"} onChange={() => set({ artistId: "any" })} />
                  <span className="opt__body">
                    <span className="opt__name">{t.book.anyArtist}</span>
                    <div className="opt__meta">{t.book.anyArtistNote}</div>
                  </span>
                </label>
              </div>
            )}

            {step === 2 && (
              <div data-step="3">
                <h3 style={{ fontSize: 20, marginBottom: 12 }}>{t.book.s3}</h3>
                <div className="datestrip">
                  {days.map((d) => {
                    const closed = d.getDay() === CLOSED_DOW;
                    const key = iso(d);
                    return (
                      <button type="button" key={key} className="datebtn" disabled={closed}
                              data-today={key === iso(new Date()) ? "true" : undefined}
                              aria-pressed={state.date === key}
                              onClick={() => set({ date: key, slot: null })}>
                        <span className="datebtn__dow">{d.toLocaleDateString(lang === "th" ? "th-TH" : "en-GB", { weekday: "short" })}</span>
                        <span className="datebtn__num">{d.getDate()}</span>
                      </button>
                    );
                  })}
                </div>
                <p className="opt__meta" style={{ marginTop: 8 }}>{t.book.closedNote}</p>
                <h3 style={{ fontSize: 20, margin: "20px 0 8px" }}>{t.book.s3slots}</h3>
                {!state.date ? <p className="muted">{t.book.pickDay}</p> : (
                  <>
                    <div className="slots" data-slots>
                      {SLOTS.map((m, i) => {
                        const st = slotState(state.date, state.artistId || "any", i);
                        const past = state.date === iso(new Date()) && m <= new Date().getHours() * 60 + new Date().getMinutes();
                        const disabled = st !== "open" || past;
                        return (
                          <button type="button" key={m} disabled={disabled}
                                  className={"slot" + (st === "hold" ? " slot--hold" : "")}
                                  data-slot-state={past ? "past" : st}
                                  aria-pressed={state.slot === m}
                                  onClick={() => set({ slot: m })}>{hhmm(m)}</button>
                        );
                      })}
                    </div>
                    <div className="slots__legend">
                      <span>● {t.book.legend.open}</span><span>◌ {t.book.legend.hold}</span><span>× {t.book.legend.taken}</span>
                    </div>
                  </>
                )}
              </div>
            )}

            {step === 3 && (
              <div data-step="4">
                <h3 style={{ fontSize: 20, marginBottom: 12 }}>{t.book.s4}</h3>
                <div className="field">
                  <label htmlFor="bk-name">{t.book.name}</label>
                  <input id="bk-name" name="name" value={state.name} onChange={(e) => set({ name: e.target.value })} />
                  {err.name && <div className="err">{t.book.required}</div>}
                </div>
                <div className="field">
                  <label htmlFor="bk-contact">{t.book.contact}</label>
                  <input id="bk-contact" name="contact" value={state.contact} onChange={(e) => set({ contact: e.target.value })} />
                  {err.contact && <div className="err">{t.book.required}</div>}
                </div>
                <div className="field">
                  <label htmlFor="bk-notes">{t.book.notes} <span className="opt__meta">({t.book.optional})</span></label>
                  <textarea id="bk-notes" name="notes" rows="3" placeholder={t.book.notesPh}
                            value={state.notes} onChange={(e) => set({ notes: e.target.value })} />
                </div>
                <div className="hp" aria-hidden="true">
                  <label htmlFor="company_url">Company URL</label>
                  <input id="company_url" name="company_url" tabIndex="-1" autoComplete="off" />
                </div>
              </div>
            )}

            <div className="book__nav">
              <button type="button" className="btn btn--ghost" disabled={step === 0} onClick={() => setStep(step - 1)}>{t.book.back}</button>
              {step < 3
                ? <button type="button" className="btn btn--accent" disabled={!canNext()} onClick={() => setStep(step + 1)}>{t.book.next}</button>
                : <button type="submit" className="btn btn--accent">{t.book.submit}</button>}
            </div>
          </form>
          <SummaryRail t={t} lang={lang} state={state} />
        </div>
      </div>
    </section>
  );
}

/* ---- visit -------------------------------------------------------------- */
function Visit({ t, lang }) {
  const [note, setNote] = useState("");
  const pins = [
    { id: "salon", x: 200, y: 120, label: t.visit.pins.salon },
    { id: "bts",   x: 92,  y: 210, label: t.visit.pins.bts },
    { id: "mall",  x: 305, y: 205, label: t.visit.pins.mall },
  ];
  return (
    <section className="section" id="visit">
      <div className="wrap">
        <Reveal>
          <p className="eyebrow">{t.visit.eyebrow}</p>
          <h2>{t.visit.title}</h2>
          <p className="muted">{t.visit.addressNote}</p>
        </Reveal>
        <div className="grid grid--2" style={{ marginTop: 28 }}>
          <div>
            <div className="map">
              <svg viewBox="0 0 400 260" role="img" aria-label={t.visit.title}>
                <rect width="400" height="260" fill="var(--ink-2)" />
                <path d="M0 215 H400" stroke="var(--line-dark)" strokeWidth="14" />
                <path d="M200 0 V260" stroke="var(--line-dark)" strokeWidth="9" />
                {pins.map((p) => (
                  <g key={p.id} className="pin" tabIndex="0" role="button" aria-label={p.label}
                     data-pin={p.id}
                     onClick={() => setNote(p.label)}
                     onKeyDown={(e) => { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); setNote(p.label); } }}>
                    <circle cx={p.x} cy={p.y} r="11" fill={p.id === "salon" ? "var(--accent)" : "var(--on-dark-soft)"} />
                    <circle cx={p.x} cy={p.y} r="4" fill="var(--ink)" />
                  </g>
                ))}
              </svg>
            </div>
            <p className="map__note" aria-live="polite">{note || t.visit.mapNote}</p>
            {/* No Google Maps href: there is no real pin to navigate to. */}
            <button className="btn btn--ghost" data-directions onClick={() => setNote(t.visit.demoNote)}>{t.visit.directions}</button>
          </div>
          <div>
            <h3 style={{ fontSize: 20 }}>{t.visit.hours}</h3>
            <p className="muted">{t.visit.hoursBody}</p>
            <h3 style={{ fontSize: 20, marginTop: 18 }}>{t.visit.finding}</h3>
            <p className="muted">{t.visit.findingBody}</p>
            <h3 style={{ fontSize: 20, margin: "26px 0 6px" }}>{t.visit.faqTitle}</h3>
            <div className="faq">
              {t.visit.faq.map(([q, a]) => (
                <details key={q}><summary>{q}</summary><p className="muted" style={{ marginTop: 10 }}>{a}</p></details>
              ))}
            </div>
          </div>
        </div>
      </div>
    </section>
  );
}

function SiteFooter({ t }) {
  return (
    <footer className="footer">
      <div className="wrap">
        <div className="footer__grid">
          <div>
            <div className="nav__name" style={{ fontSize: 24 }}>{BRAND}</div>
            <p className="muted" style={{ marginTop: 10 }}>{t.visit.hoursBody}</p>
          </div>
          <div>
            <p className="eyebrow">{t.footer.by}</p>
            <p><a href="https://saangstudio.com">{t.footer.byLink}</a></p>
          </div>
          <div>
            <p className="eyebrow">{t.footer.want}</p>
            <p><a href="https://saangstudio.com/en/nail-salons">{t.footer.wantCta}</a></p>
          </div>
        </div>
        {/* req009: disclosure #4 of 4. */}
        <p className="footer__note" data-concept-footer>{t.footer.disclosure}</p>
      </div>
    </footer>
  );
}

function StickyCta({ t }) {
  return (
    <div className="sticky">
      <a className="btn btn--ghost" href={PHONE_TEL} style={{ color: "var(--on-dark)" }}>{t.sticky.call}</a>
      <button className="btn btn--accent" onClick={() => goto("book")}>{t.sticky.book}</button>
    </div>
  );
}

/* ---- app ---------------------------------------------------------------- */
function App() {
  const [lang, setLang] = useState(initialLang);
  const [shade, setShade] = useState(SHADES[0]);
  const t = DICT[lang];

  useEffect(() => {
    enhanceSeo(lang);
    try { localStorage.setItem("lacq_lang", lang); } catch (e) {}
  }, [lang]);

  useEffect(() => {
    if (REDUCED || !window.Lenis) return;
    const l = new window.Lenis({ duration: 1.05 });
    let id; const raf = (time) => { l.raf(time); id = requestAnimationFrame(raf); };
    id = requestAnimationFrame(raf);
    return () => { cancelAnimationFrame(id); l.destroy(); };
  }, []);

  const toggleLang = useCallback(() => setLang((l) => (l === "en" ? "th" : "en")), []);

  return (
    <>
      <Ribbon t={t} />
      <Nav t={t} lang={lang} toggleLang={toggleLang} />
      <Hero t={t} lang={lang} shade={shade} setShade={setShade} />
      <Work t={t} lang={lang} />
      <Menu t={t} lang={lang} />
      <Team t={t} lang={lang} />
      <Book t={t} lang={lang} />
      <Band t={t} id="hygiene" block={t.hygiene} dark />
      <Band t={t} id="care" block={t.care} />
      <Visit t={t} lang={lang} />
      <SiteFooter t={t} />
      <StickyCta t={t} />
    </>
  );
}

ReactDOM.createRoot(document.getElementById("root")).render(<App />);
