// M&T Surface Protectors — Mobile shared components.
// Exposes: Nav, MobileMenu, StickyCTA, MobileFooter, FinalCTA, TrustBar,
//          InquirySheet (bottom-sheet wizard), TweaksPanel (mobile),
//          useReveal, Reveal, WordReveal, Typewriter, LineEyebrow, Stars,
//          SwipeCarousel, useScrollDirection.

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

// ─────────────────────────────────────────────
// Address helpers (mobile) — Places API (New) via fetch
// ─────────────────────────────────────────────
function parseAddressComponentsM(components) {
  let streetNumber = "", route = "", subpremise = "";
  let city = "", state = "", zip = "", zipExt = "";
  for (const c of components) {
    const t = c.types || [];
    if (t.includes("street_number")) streetNumber = c.long_name;
    else if (t.includes("route")) route = c.long_name;
    else if (t.includes("subpremise")) subpremise = c.long_name;
    else if (t.includes("locality")) city = c.long_name;
    else if (!city && (t.includes("sublocality") || t.includes("sublocality_level_1") || t.includes("neighborhood"))) city = c.long_name;
    else if (!city && t.includes("postal_town")) city = c.long_name;
    else if (!city && t.includes("administrative_area_level_3")) city = c.long_name;
    else if (t.includes("administrative_area_level_1")) state = c.short_name;
    else if (t.includes("postal_code")) zip = c.long_name;
    else if (t.includes("postal_code_suffix")) zipExt = c.long_name;
  }
  return {
    line1: [streetNumber, route].filter(Boolean).join(" "),
    line2: subpremise,
    city, state,
    zip: zipExt && zip ? `${zip}-${zipExt}` : zip,
  };
}

const M_US_STATES = [
  ["AL","Alabama"],["AK","Alaska"],["AZ","Arizona"],["AR","Arkansas"],["CA","California"],
  ["CO","Colorado"],["CT","Connecticut"],["DE","Delaware"],["DC","District of Columbia"],
  ["FL","Florida"],["GA","Georgia"],["HI","Hawaii"],["ID","Idaho"],["IL","Illinois"],
  ["IN","Indiana"],["IA","Iowa"],["KS","Kansas"],["KY","Kentucky"],["LA","Louisiana"],
  ["ME","Maine"],["MD","Maryland"],["MA","Massachusetts"],["MI","Michigan"],
  ["MN","Minnesota"],["MS","Mississippi"],["MO","Missouri"],["MT","Montana"],
  ["NE","Nebraska"],["NV","Nevada"],["NH","New Hampshire"],["NJ","New Jersey"],
  ["NM","New Mexico"],["NY","New York"],["NC","North Carolina"],["ND","North Dakota"],
  ["OH","Ohio"],["OK","Oklahoma"],["OR","Oregon"],["PA","Pennsylvania"],
  ["RI","Rhode Island"],["SC","South Carolina"],["SD","South Dakota"],["TN","Tennessee"],
  ["TX","Texas"],["UT","Utah"],["VT","Vermont"],["VA","Virginia"],["WA","Washington"],
  ["WV","West Virginia"],["WI","Wisconsin"],["WY","Wyoming"]
];
const M_US_STATE_CODES = new Set(M_US_STATES.map(([c]) => c));
const mIsValidZip = (s) => /^\d{5}(-\d{4})?$/.test((s || "").trim());
const mIsValidState = (s) => M_US_STATE_CODES.has((s || "").trim().toUpperCase());


// ─────────────────────────────────────────────
// Reveal-on-scroll
// ─────────────────────────────────────────────
function useReveal(opts = {}) {
  const ref = useRef(null);
  const [shown, setShown] = useState(false);
  useEffect(() => {
    const el = ref.current; if (!el) return;
    const io = new IntersectionObserver(
      (entries) => entries.forEach(e => { if (e.isIntersecting) { setShown(true); io.disconnect(); } }),
      { threshold: opts.threshold ?? 0.12, rootMargin: opts.rootMargin ?? "0px 0px -60px 0px" }
    );
    io.observe(el);
    return () => io.disconnect();
  }, []);
  return [ref, shown];
}

function Reveal({ children, delay = 0, y = 18, dur = 900, className = "", style = {} }) {
  const [ref, shown] = useReveal();
  return (
    <div ref={ref} className={className} style={{
      opacity: shown ? 1 : 0,
      transform: shown ? "none" : `translateY(${y}px)`,
      transition: `opacity ${dur}ms cubic-bezier(.2,.7,.2,1) ${delay}ms, transform ${dur}ms cubic-bezier(.2,.7,.2,1) ${delay}ms`,
      ...style,
    }}>{children}</div>
  );
}

function WordReveal({ text, delay = 0, stagger = 60, italicWords = [], style = {} }) {
  const [ref, shown] = useReveal();
  const words = text.split(" ");
  return (
    <span ref={ref} style={{ display: "inline", ...style }}>
      {words.map((w, i) => {
        const isItalic = italicWords.includes(w.replace(/[^\w]/g, ""));
        return (
          <span key={i} style={{
            display: "inline-block",
            transform: shown ? "translateY(0)" : "translateY(35%)",
            opacity: shown ? 1 : 0,
            transition: `transform 900ms cubic-bezier(.2,.7,.2,1) ${delay + i * stagger}ms, opacity 700ms ease ${delay + i * stagger}ms`,
            fontStyle: isItalic ? "italic" : "normal",
            color: isItalic ? "var(--maroon)" : "inherit",
            marginRight: "0.22em",
          }}>{w}</span>
        );
      })}
    </span>
  );
}

function Typewriter({ words, typeSpeed = 100, deleteSpeed = 50, holdMs = 1500, startDelay = 500, style = {} }) {
  const [display, setDisplay] = useState("");
  const [wordIdx, setWordIdx] = useState(0);
  const [phase, setPhase] = useState("typing");
  const [started, setStarted] = useState(false);
  useEffect(() => {
    const t = setTimeout(() => setStarted(true), startDelay);
    return () => clearTimeout(t);
  }, [startDelay]);
  useEffect(() => {
    if (!started) return;
    const cur = words[wordIdx];
    let t;
    if (phase === "typing") {
      if (display.length < cur.length) t = setTimeout(() => setDisplay(cur.slice(0, display.length + 1)), typeSpeed);
      else t = setTimeout(() => setPhase("deleting"), holdMs);
    } else if (phase === "deleting") {
      if (display.length > 0) t = setTimeout(() => setDisplay(cur.slice(0, display.length - 1)), deleteSpeed);
      else { setWordIdx((wordIdx + 1) % words.length); setPhase("typing"); }
    }
    return () => clearTimeout(t);
  }, [display, phase, wordIdx, started, words, typeSpeed, deleteSpeed, holdMs]);
  return (
    <span style={{ ...style, display: "inline-block" }}>
      <em style={{ fontStyle: "italic", color: "inherit" }}>{display}</em>
      <span aria-hidden="true" style={{
        display: "inline-block", width: "0.04em", height: "0.78em",
        marginLeft: "0.05em", marginBottom: "-0.06em",
        background: "currentColor", verticalAlign: "baseline",
        animation: "tw-blink 900ms steps(2,start) infinite",
      }}/>
      <style>{`@keyframes tw-blink { 0%,100%{opacity:1;} 50%{opacity:0;} }`}</style>
    </span>
  );
}

function LineEyebrow({ children, style = {} }) {
  const [ref, shown] = useReveal();
  return (
    <div ref={ref} className="eyebrow" style={{ display: "flex", alignItems: "center", gap: 10, ...style }}>
      <span style={{
        display: "inline-block", height: 1, background: "currentColor",
        width: shown ? 22 : 0,
        transition: "width 800ms cubic-bezier(.2,.7,.2,1)",
        opacity: 0.7,
      }}/>
      <span style={{
        opacity: shown ? 1 : 0,
        transform: shown ? "none" : "translateX(-6px)",
        transition: "opacity .7s ease 200ms, transform .7s ease 200ms",
      }}>{children}</span>
    </div>
  );
}

function Stars({ n = 5, color = "var(--ink)" }) {
  return (
    <span className="stars" aria-hidden="true">
      {Array.from({ length: n }).map((_, i) => (
        <svg key={i} viewBox="0 0 11 11" style={{ fill: color }}>
          <path d="M5.5 0l1.6 3.5 3.9.4-2.9 2.6.9 3.8L5.5 8.4 2 10.3l.9-3.8L0 3.9l3.9-.4z"/>
        </svg>
      ))}
    </span>
  );
}

// ─────────────────────────────────────────────
// Scroll direction — used to hide/show sticky CTA
// ─────────────────────────────────────────────
function useScrollDirection({ threshold = 8, minY = 200 } = {}) {
  const [hidden, setHidden] = useState(false);
  useEffect(() => {
    let lastY = window.scrollY;
    let acc = 0;
    const onScroll = () => {
      const y = window.scrollY;
      const dy = y - lastY;
      if (y < minY) { setHidden(false); lastY = y; return; }
      // accumulate small same-direction deltas so jitter doesn't flicker
      if ((acc > 0 && dy < 0) || (acc < 0 && dy > 0)) acc = 0;
      acc += dy;
      if (acc > threshold) { setHidden(true); acc = 0; }
      else if (acc < -threshold) { setHidden(false); acc = 0; }
      lastY = y;
    };
    window.addEventListener("scroll", onScroll, { passive: true });
    return () => window.removeEventListener("scroll", onScroll);
  }, [threshold, minY]);
  return hidden;
}

// ─────────────────────────────────────────────
// Nav + fullscreen menu
// ─────────────────────────────────────────────
function Nav({ onInquire, openMenu, current = "" }) {
  const [scrolled, setScrolled] = useState(false);
  useEffect(() => {
    const on = () => setScrolled(window.scrollY > 24);
    on();
    window.addEventListener("scroll", on, { passive: true });
    return () => window.removeEventListener("scroll", on);
  }, []);
  return (
    <header className={"m-nav" + (scrolled ? " scrolled" : "")}>
      <a href="/mobile" className="m-nav__logo">
        <img src="../assets/logo.png" alt="M&T" />
        <span>M<span style={{ fontSize: 10, verticalAlign: "super", margin: "0 1px" }}>&amp;</span>T</span>
      </a>
      <button
        className="m-nav__burger"
        aria-label="Open menu"
        onClick={openMenu}
      >
        <span/><span/><span/>
      </button>
    </header>
  );
}

function MobileMenu({ open, onClose, onInquire, current = "" }) {
  // Root-absolute hrefs: relative ones break from the clean URL /mobile
  // (no trailing slash), resolving against the site root → desktop pages.
  const links = [
    { label: "Home",     href: "/mobile",          id: "index",    sub: "01" },
    { label: "Work",     href: "/mobile/work",     id: "work",     sub: "02" },
    { label: "Services", href: "/mobile/services", id: "services", sub: "03" },
    { label: "Reviews",  href: "/mobile#reviews",  id: null,       sub: "04" },
    { label: "Learn",    href: "/mobile/learn",    id: "learn",    sub: "05" },
    { label: "FAQ",      href: "/mobile/faq",      id: "faq",      sub: "06" },
  ];
  // Lock scroll while menu is open
  useEffect(() => {
    document.body.style.overflow = open ? "hidden" : "";
    return () => { document.body.style.overflow = ""; };
  }, [open]);

  // Close the menu on any nav. For a #section link that targets the page we're
  // already on, don't reload — smooth-scroll to it after the menu closes (so the
  // scroll lock is lifted). Cross-page #links navigate normally; the destination
  // page scrolls to the hash on load.
  const handleNav = (e, href) => {
    onClose();
    const hashIdx = href.indexOf("#");
    if (hashIdx === -1) return;
    const id = href.slice(hashIdx + 1);
    // If the target section is on THIS page, smooth-scroll instead of reloading.
    // Otherwise let the link navigate; the destination scrolls to it on load.
    if (!document.getElementById(id)) return;
    e.preventDefault();
    setTimeout(() => {
      document.body.style.overflow = ""; // ensure the menu's scroll-lock is lifted first
      const el = document.getElementById(id);
      if (!el) return;
      const navH = parseInt(getComputedStyle(document.documentElement).getPropertyValue("--nav-h")) || 64;
      window.scrollTo({ top: Math.max(0, el.getBoundingClientRect().top + window.scrollY - navH - 8), behavior: "smooth" });
    }, 380);
  };
  return (
    <div className={"m-menu" + (open ? " open" : "")} aria-hidden={!open}>
      <div className="m-menu__head">
        <div className="m-nav__logo">
          <img src="../assets/logo.png" alt="M&T" style={{ height: 30 }}/>
          <span style={{ fontFamily: "'Instrument Serif',serif", fontSize: 14, letterSpacing: "-0.01em" }}>
            M<span style={{ fontSize: 10, verticalAlign: "super", margin: "0 1px" }}>&amp;</span>T Surface Protectors
          </span>
        </div>
        <button
          className={"m-nav__burger open"}
          aria-label="Close menu"
          onClick={onClose}
        >
          <span/><span/><span/>
        </button>
      </div>
      <div className="m-menu__body">
        <div style={{ marginBottom: 8 }}>
          {links.map((l, i) => {
            const isCurrent = current === l.id;
            return (
              <a
                key={l.href}
                href={l.href}
                onClick={(e) => handleNav(e, l.href)}
                className="m-menu__link"
                style={{
                  opacity: open ? 1 : 0,
                  transform: open ? "none" : "translateY(20px)",
                  transitionDelay: `${open ? 200 + i * 50 : 0}ms`,
                  color: isCurrent ? "var(--maroon)" : "var(--ink)",
                }}
              >
                <span style={{ display: "inline-flex", alignItems: "baseline", gap: 14 }}>
                  <em>{l.sub}</em>
                  {l.label}
                </span>
                <svg width="14" height="14" viewBox="0 0 14 14" fill="none" style={{ opacity: 0.4 }}>
                  <path d="M1 7h12m0 0L7 1m6 6L7 13" stroke="currentColor" strokeWidth="1.2"/>
                </svg>
              </a>
            );
          })}
        </div>
        <div style={{ marginTop: 32, opacity: open ? 1 : 0, transition: "opacity .5s ease .6s" }}>
          <div className="eyebrow" style={{ marginBottom: 14 }}>Contact</div>
          <a href="tel:+19088661919" style={{ display: "block", fontFamily: "'Instrument Serif',serif", fontSize: 22, letterSpacing: "-0.01em", marginBottom: 6 }}>
            (908) 866-1919
          </a>
          <a href="mailto:sales@mtsurfaceprotectors.com" style={{ fontSize: 13, color: "var(--mute)" }}>
            sales@mtsurfaceprotectors.com
          </a>
          <div style={{ marginTop: 28, fontSize: 12, color: "var(--mute)", lineHeight: 1.6 }}>
            Certified StoneGuard® specialists<br/>NY · NJ · PA
          </div>
        </div>
      </div>
      <div className="m-menu__foot">
        <button onClick={() => { onClose(); onInquire(); }} className="btn btn-primary btn-block">
          Get My Stone Protected
          <svg width="14" height="14" viewBox="0 0 14 14" fill="none"><path d="M1 7h12m0 0L7 1m6 6L7 13" stroke="currentColor" strokeWidth="1.3"/></svg>
        </button>
      </div>
    </div>
  );
}

// ─────────────────────────────────────────────
// Sticky CTA (auto-hides on scroll down)
// ─────────────────────────────────────────────
function StickyCTA({ onInquire, label = "Get My Stone Protected", enabled = true }) {
  const hidden = useScrollDirection();
  if (!enabled) return null;
  return (
    <div className={"m-sticky" + (hidden ? " hidden" : "")} role="region" aria-label="Quick actions">
      <button className="m-sticky__btn" onClick={onInquire}>
        {label}
        <svg width="12" height="12" viewBox="0 0 12 12" fill="none"><path d="M1 6h10m0 0L6 1m5 5L6 11" stroke="currentColor" strokeWidth="1.3"/></svg>
      </button>
      <a className="m-sticky__call" href="tel:+19088661919" aria-label="Call (908) 866-1919">
        <svg width="18" height="18" viewBox="0 0 18 18" fill="none">
          <path d="M3.5 2.5h3l1.5 4-2 1.2c.8 1.7 2.1 3 3.8 3.8l1.2-2 4 1.5v3a1 1 0 0 1-1 1A11 11 0 0 1 2.5 3.5a1 1 0 0 1 1-1Z" stroke="currentColor" strokeWidth="1.3" strokeLinejoin="round"/>
        </svg>
      </a>
    </div>
  );
}

// ─────────────────────────────────────────────
// TrustBar (marquee, compact)
// ─────────────────────────────────────────────
function TrustBar() {
  const items = [
    { label: "Homes",     value: "Thousands" },
    { label: "Google",    value: "5.0", star: true },
    { label: "Houzz",     value: "5.0", star: true },
    { label: "Warranty",  value: "Lifetime" },
    { label: "Service",   value: "NY · NJ · PA" },
  ];
  const loop = [...items, ...items, ...items];
  return (
    <section style={{
      borderTop: "1px solid var(--line)",
      borderBottom: "1px solid var(--line)",
      background: "var(--cream)",
      overflow: "hidden",
      position: "relative",
    }}>
      <div style={{
        maskImage: "linear-gradient(90deg, transparent 0, #000 40px, #000 calc(100% - 40px), transparent 100%)",
        WebkitMaskImage: "linear-gradient(90deg, transparent 0, #000 40px, #000 calc(100% - 40px), transparent 100%)",
      }}>
        <div style={{
          display: "flex", alignItems: "center",
          padding: "20px 0",
          width: "max-content",
          animation: "tb-marquee 35s linear infinite",
        }}>
          {loop.map((it, i) => (
            <div key={i} style={{
              display: "inline-flex", alignItems: "baseline", gap: 12,
              padding: "0 28px",
              whiteSpace: "nowrap", flexShrink: 0,
              borderRight: "1px solid var(--line)",
            }}>
              <span className="mono" style={{
                fontSize: 9, letterSpacing: "0.2em", textTransform: "uppercase",
                color: "var(--mute)",
              }}>{it.label}</span>
              <span className="serif" style={{
                fontSize: 20, letterSpacing: "-0.01em", display: "inline-flex", alignItems: "center", gap: 4,
              }}>
                {it.value}
                {it.star && (
                  <svg width="13" height="13" viewBox="0 0 11 11" fill="var(--ink)">
                    <path d="M5.5 0l1.6 3.5 3.9.4-2.9 2.6.9 3.8L5.5 8.4 2 10.3l.9-3.8L0 3.9l3.9-.4z"/>
                  </svg>
                )}
              </span>
            </div>
          ))}
        </div>
      </div>
      <style>{`@keyframes tb-marquee { 0%{transform:translateX(0);} 100%{transform:translateX(-33.333%);} }`}</style>
    </section>
  );
}

// ─────────────────────────────────────────────
// FinalCTA (mobile)
// ─────────────────────────────────────────────
function FinalCTA({ onInquire, ctaPrimary = "Get My Stone Protected", ctaPromise = "Tell us about your stone and we'll send back a free, custom protection plan and quote." }) {
  return (
    <section style={{ background: "var(--ink)", color: "var(--bone)", padding: "80px 0 80px" }}>
      <div className="container">
        <Reveal>
          <div className="eyebrow" style={{ color: "rgba(245,242,237,0.5)", marginBottom: 24 }}>
            <span style={{ display: "inline-block", width: 22, height: 1, background: "currentColor", marginRight: 10, verticalAlign: "middle", opacity: 0.7 }}/>
            Your next step
          </div>
        </Reveal>
        <h2 className="serif" style={{
          fontSize: 44, lineHeight: 0.98, letterSpacing: "-0.02em",
          marginBottom: 24,
        }}>
          <Reveal>You chose the stone.</Reveal>
          <Reveal delay={120}>
            <span style={{ color: "var(--bronze)", fontStyle: "italic", display: "block" }}>
              We make sure you can{" "}
              <Typewriter words={["use it.", "keep it.", "love it."]} typeSpeed={85} deleteSpeed={45} holdMs={1600} startDelay={500} />
            </span>
          </Reveal>
        </h2>
        <Reveal delay={200}>
          <p style={{ fontSize: 15, lineHeight: 1.55, color: "rgba(245,242,237,0.7)", marginBottom: 32, maxWidth: 420 }}>
            {ctaPromise}
          </p>
        </Reveal>
        <Reveal delay={260}>
          <button onClick={onInquire} className="btn btn-block" style={{ background: "var(--bone)", color: "var(--ink)", marginBottom: 12 }}>
            {ctaPrimary}
            <svg width="13" height="13" viewBox="0 0 14 14" fill="none"><path d="M1 7h12m0 0L7 1m6 6L7 13" stroke="currentColor" strokeWidth="1.3"/></svg>
          </button>
          <a href="tel:+19088661919" className="btn btn-block" style={{ border: "1px solid rgba(245,242,237,0.25)", color: "var(--bone)" }}>
            <svg width="14" height="14" viewBox="0 0 18 18" fill="none">
              <path d="M3.5 2.5h3l1.5 4-2 1.2c.8 1.7 2.1 3 3.8 3.8l1.2-2 4 1.5v3a1 1 0 0 1-1 1A11 11 0 0 1 2.5 3.5a1 1 0 0 1 1-1Z" stroke="currentColor" strokeWidth="1.3" strokeLinejoin="round"/>
            </svg>
            Call Michael · (908) 866-1919
          </a>
        </Reveal>
        <Reveal delay={340}>
          <div style={{
            marginTop: 28,
            display: "grid", gridTemplateColumns: "1fr 1fr", gap: "12px 16px",
            color: "rgba(245,242,237,0.55)", fontSize: 12,
          }}>
            <span>✓ Free written quote</span>
            <span>✓ No obligation</span>
            <span>✓ Lifetime warranty</span>
          </div>
        </Reveal>
      </div>
    </section>
  );
}

// ─────────────────────────────────────────────
// Mobile footer
// ─────────────────────────────────────────────
function MobileFooter() {
  return (
    <footer style={{ background: "var(--black)", color: "rgba(245,242,237,0.7)", padding: "48px 0 32px" }}>
      <div className="container">
        <div className="serif" style={{ fontSize: 28, color: "var(--bone)", letterSpacing: "-0.01em", marginBottom: 14, lineHeight: 1.05 }}>
          M<span style={{ fontSize: 16, verticalAlign: "super", margin: "0 2px" }}>&amp;</span>T Surface Protectors
        </div>
        <p style={{ fontSize: 13, lineHeight: 1.6, marginBottom: 32, maxWidth: 320 }}>
          Certified StoneGuard® specialists, serving fine homes across NY, NJ, and PA since 2020.
        </p>

        {/* Accordion-style column groups */}
        <div style={{ borderTop: "1px solid rgba(245,242,237,0.12)" }}>
          {[
            { t: "Services", l: [
              { label: "StoneGuard® Clear",   href: "/mobile/services#clear" },
              { label: "StoneGuard® Satin",   href: "/mobile/services#satin" },
              { label: "Stain & Etch Removal", href: "/mobile/services" },
              { label: "Stone Restoration",   href: "/mobile/services" },
            ]},
            { t: "Company",  l: [
              { label: "About",     href: "../about.html" },
              { label: "Our Work",  href: "/mobile/work" },
              { label: "Reviews",   href: "/mobile#reviews" },
              { label: "FAQ",       href: "/mobile/faq" },
            ]},
            { t: "Connect",  l: [
              { label: "Instagram", href: "https://www.instagram.com/mt_surfaceprotectors/" },
              { label: "TikTok",    href: "https://www.tiktok.com/@mt_surfaceprotectors" },
              { label: "Facebook",  href: "https://www.facebook.com/mtsurfaceprotectors/" },
              { label: "YouTube",   href: "https://www.youtube.com/@mt_surfaceprotectors" },
            ]},
          ].map((col) => (
            <FooterAcc key={col.t} title={col.t} items={col.l} />
          ))}
        </div>

        <div style={{ marginTop: 32, paddingTop: 24, borderTop: "1px solid rgba(245,242,237,0.12)", display: "flex", flexDirection: "column", gap: 8 }}>
          <a href="tel:+19088661919" style={{ fontSize: 14, color: "var(--bone)" }}>(908) 866-1919</a>
          <a href="mailto:sales@mtsurfaceprotectors.com" style={{ fontSize: 13 }}>sales@mtsurfaceprotectors.com</a>
        </div>
        <div style={{ marginTop: 20, display: "flex", gap: 20, fontSize: 12 }}>
          <a href="/privacy">Privacy</a>
          <a href="/sms-terms">SMS Terms</a>
        </div>
        <div className="mono" style={{ marginTop: 24, fontSize: 10, letterSpacing: "0.14em", color: "rgba(245,242,237,0.4)" }}>
          © 2026 M&amp;T Surface Protectors LLC
        </div>
      </div>
    </footer>
  );
}

function FooterAcc({ title, items }) {
  const [open, setOpen] = useState(false);
  return (
    <div style={{ borderBottom: "1px solid rgba(245,242,237,0.12)" }}>
      <button
        onClick={() => setOpen(!open)}
        style={{
          width: "100%", padding: "16px 0",
          display: "flex", alignItems: "center", justifyContent: "space-between",
          fontFamily: "'JetBrains Mono', monospace", fontSize: 11, letterSpacing: "0.18em",
          textTransform: "uppercase", color: "rgba(245,242,237,0.7)",
        }}
      >
        {title}
        <span style={{
          display: "inline-block", width: 12, height: 1, position: "relative",
          background: "currentColor",
        }}>
          <span style={{
            position: "absolute", inset: 0, background: "currentColor",
            transform: open ? "rotate(0deg)" : "rotate(90deg)",
            transition: "transform .3s",
          }}/>
        </span>
      </button>
      <div style={{
        display: "grid",
        gridTemplateRows: open ? "1fr" : "0fr",
        transition: "grid-template-rows .35s ease",
      }}>
        <div style={{ overflow: "hidden" }}>
          <ul style={{ listStyle: "none", paddingBottom: 16, display: "grid", gap: 10 }}>
            {items.map(i => (
              <li key={i.label}>
                <a
                  href={i.href}
                  target={i.href.startsWith("http") ? "_blank" : undefined}
                  rel={i.href.startsWith("http") ? "noopener noreferrer" : undefined}
                  style={{ fontSize: 14 }}
                >{i.label}</a>
              </li>
            ))}
          </ul>
        </div>
      </div>
    </div>
  );
}

// ─────────────────────────────────────────────
// Swipe carousel — generic, snap-x with dots + maroon progress bar
// ─────────────────────────────────────────────
function SwipeCarousel({
  items,
  renderItem,
  showDots = true,
  showProgress = true,
  className = "",
  itemClass = "",
  gap = 12,
  padX = 20,
  width = "82%",
}) {
  const ref = useRef(null);
  const [active, setActive] = useState(0);
  const onScroll = () => {
    const el = ref.current; if (!el) return;
    const w = el.querySelector("[data-snap-item]")?.offsetWidth || 1;
    const idx = Math.round(el.scrollLeft / (w + gap));
    setActive(Math.min(items.length - 1, Math.max(0, idx)));
  };
  const scrollTo = (i) => {
    const el = ref.current; if (!el) return;
    const w = el.querySelector("[data-snap-item]")?.offsetWidth || 1;
    el.scrollTo({ left: i * (w + gap), behavior: "smooth" });
  };
  return (
    <div className={className}>
      <div
        ref={ref}
        className="no-scrollbar"
        onScroll={onScroll}
        style={{
          display: "flex",
          gap,
          overflowX: "auto",
          scrollSnapType: "x mandatory",
          scrollPaddingLeft: padX,
          padding: `0 ${padX}px 12px`,
          WebkitOverflowScrolling: "touch",
        }}
      >
        {items.map((it, i) => (
          <div
            key={i}
            data-snap-item
            className={itemClass}
            style={{
              flex: `0 0 ${width}`,
              scrollSnapAlign: "start",
            }}
          >
            {renderItem(it, i, active === i)}
          </div>
        ))}
      </div>

      {(showProgress || showDots) && (
        <div className="container" style={{ display: "flex", alignItems: "center", gap: 14, marginTop: 14 }}>
          {showProgress && (
            <div style={{ flex: 1, height: 2, background: "rgba(20,20,18,0.1)", position: "relative" }}>
              <div style={{
                position: "absolute", top: 0, left: 0, height: "100%",
                width: `${((active + 1) / items.length) * 100}%`,
                background: "var(--maroon)",
                transition: "width .35s cubic-bezier(.2,.7,.2,1)",
              }}/>
            </div>
          )}
          {showDots && (
            <div className="mono" style={{ fontSize: 10, letterSpacing: "0.18em", color: "var(--mute)" }}>
              <span style={{ color: "var(--ink)", fontVariantNumeric: "tabular-nums" }}>{String(active + 1).padStart(2, "0")}</span>
              {" / "}
              {String(items.length).padStart(2, "0")}
            </div>
          )}
        </div>
      )}
    </div>
  );
}

// ─────────────────────────────────────────────
// Inquiry bottom sheet — 4 steps:
//   1. Stone + finish
//   2. Rooms + square footage
//   3. Timeline + optional notes
//   4. Contact
// ─────────────────────────────────────────────
// File → base64 (mobile)
const mFileToDataURL = (file) => new Promise((res, rej) => {
  const fr = new FileReader();
  fr.onload = () => res(fr.result);
  fr.onerror = rej;
  fr.readAsDataURL(file);
});

const M_STEPS = [
  { eb: "Location",      title: "Where are you located?" },
  { eb: "Your stone",    title: "What type of stone?" },
  { eb: "Surfaces",      title: "Which surfaces, and how big?" },
  { eb: "Situation",     title: "What's the situation?" },
  { eb: "Timeline",      title: "When do you need it done?" },
  { eb: "Discovery",     title: "How did you find us?" },
  { eb: "Details",       title: "Photos or a message?" },
  { eb: "Contact",       title: "How can we reach you?" },
];

function InquirySheet({ open, onClose, ctaPromise }) {
  const [step, setStep] = useState(0);
  const [data, setData] = useState({
    location: "",
    stone: "", stoneOther: "",
    surfaces: [], surfacesOther: "",
    sqft: 25, sqftKnown: false,
    situation: "",
    timeline: "",
    howHeard: "", howHeardOther: "",
    photos: [], message: "",
    name: "", email: "", phone: "",
    addressLine1: "", addressLine2: "",
    addressCity: "", addressState: "", addressZip: "",
    addressFormatted: "",
    smsConsent: false,
    website: "", // honeypot
  });
  const [touched, setTouched] = useState({});
  const [submitting, setSubmitting] = useState(false);
  const [serverError, setServerError] = useState(null);
  const startedAtRef = useRef(null);

  const upd = (k, v) => setData(d => ({ ...d, [k]: v }));
  const blurMe = (k) => setTouched(t => ({ ...t, [k]: true }));
  const toggle = (k, v) => setData(d => {
    const arr = d[k] || [];
    return { ...d, [k]: arr.includes(v) ? arr.filter(x => x !== v) : [...arr, v] };
  });
  useEffect(() => {
    document.body.style.overflow = open ? "hidden" : "";
    if (open) {
      startedAtRef.current = Date.now();
      setServerError(null);
      if (typeof window !== "undefined" && window.mtTrack) {
        window.mtTrack("StartInquiry", { source: "mobile" }, null, true);
      }
    }
    return () => { document.body.style.overflow = ""; };
  }, [open]);

  const total = M_STEPS.length;
  const reset = () => {
    setStep(0); setTouched({}); setServerError(null);
    setData({
      location: "",
      stone: "", stoneOther: "",
      surfaces: [], surfacesOther: "",
      sqft: 25, sqftKnown: false,
      situation: "",
      timeline: "",
      howHeard: "", howHeardOther: "",
      photos: [], message: "",
      name: "", email: "", phone: "",
      addressLine1: "", addressLine2: "",
      addressCity: "", addressState: "", addressZip: "",
      addressFormatted: "",
      smsConsent: false, website: "",
    });
  };
  const close = () => { onClose(); setTimeout(reset, 400); };

  // Validation helpers
  const digits = (s) => (s || "").replace(/\D/g, "");
  const validEmail = (s) => /^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/.test((s || "").trim());
  const validPhone = (s) => {
    const d = digits(s); return d.length === 10 || (d.length === 11 && d[0] === "1");
  };
  const fmtPhone = (s) => {
    let d = digits(s);
    if (d.length === 11 && d[0] === "1") d = d.slice(1);
    d = d.slice(0, 10);
    if (d.length === 0) return "";
    if (d.length < 4) return "(" + d;
    if (d.length < 7) return `(${d.slice(0,3)}) ${d.slice(3)}`;
    return `(${d.slice(0,3)}) ${d.slice(3,6)}-${d.slice(6)}`;
  };

  const fieldErrors = {
    name: data.name.trim() ? null : "Please enter your name.",
    email: validEmail(data.email) ? null : "Please enter a valid email.",
    phone: validPhone(data.phone) ? null : "Please enter a valid US phone number.",
    addressLine1: data.addressLine1.trim() ? null : "Please enter your street address.",
    addressCity:  data.addressCity.trim()  ? null : "Please enter a city.",
    addressState: mIsValidState(data.addressState) ? null : "Please pick a state.",
    addressZip:   mIsValidZip(data.addressZip)     ? null : "Please enter a valid ZIP.",
  };
  const contactValid = !fieldErrors.name && !fieldErrors.email && !fieldErrors.phone
    && !fieldErrors.addressLine1 && !fieldErrors.addressCity && !fieldErrors.addressState && !fieldErrors.addressZip;

  const canAdvance =
    (step === 0 && !!data.location) ||
    (step === 1 && !!data.stone && (data.stone !== "Other" || data.stoneOther.trim().length > 0)) ||
    (step === 2 && data.surfaces.length > 0 && (!data.surfaces.includes("Other") || data.surfacesOther.trim().length > 0)) ||
    (step === 3 && !!data.situation) ||
    (step === 4 && !!data.timeline) ||
    (step === 5 && !!data.howHeard && (data.howHeard !== "Other" || data.howHeardOther.trim().length > 0)) ||
    (step === 6) ||
    (step === 7 && contactValid);

  const isOutOfArea = data.location === "Other";

  const submitInquiry = async () => {
    setTouched({
      name: true, email: true, phone: true,
      addressLine1: true, addressCity: true, addressState: true, addressZip: true,
    });
    if (!contactValid) return;
    const tooFast = Date.now() - (startedAtRef.current || 0) < 3000;
    if (data.website || tooFast) { setStep(total); return; }

    setSubmitting(true); setServerError(null);

    // Meta Pixel/CAPI dedup: shared event_id for the browser + server Lead.
    const eventId = (window.crypto && window.crypto.randomUUID)
      ? window.crypto.randomUUID()
      : String(Date.now()) + Math.random();
    const fbCookies = (window.mtFbCookies && window.mtFbCookies()) || {};

    const payload = {
      location: data.location,
      route: data.location === "Other" ? "stoneguard" : "mt",

      event_id: eventId,
      fbp: fbCookies.fbp || null,
      fbc: fbCookies.fbc || null,

      stone: data.stone,
      stone_other: data.stone === "Other" ? data.stoneOther.trim() : null,

      surfaces: data.surfaces.filter(s => s !== "Other"),
      surfaces_other: data.surfaces.includes("Other") ? data.surfacesOther.trim() : null,
      sqft: data.sqftKnown ? data.sqft : null,
      sqft_known: !!data.sqftKnown,

      situation: data.situation,
      timeline: data.timeline,
      how_heard: data.howHeard,
      how_heard_other: data.howHeard === "Other" ? data.howHeardOther.trim() : null,

      message: data.message.trim(),
      photos: data.photos.map(p => ({
        name: p.name, size: p.size, type: p.type, data_url: p.dataUrl,
      })),

      name: data.name.trim(),
      email: data.email.trim(),
      phone: digits(data.phone),
      phone_formatted: data.phone.trim(),
      address_line1: data.addressLine1.trim(),
      address_line2: data.addressLine2.trim(),
      address_city:  data.addressCity.trim(),
      address_state: data.addressState.trim().toUpperCase(),
      address_zip:   data.addressZip.trim(),
      address_formatted: data.addressFormatted ||
        [
          [data.addressLine1, data.addressLine2].filter(Boolean).join(" "),
          data.addressCity,
          [data.addressState, data.addressZip].filter(Boolean).join(" "),
        ].filter(Boolean).join(", "),
      sms_consent: !!data.smsConsent,

      source: "mobile",
      page: typeof window !== "undefined" ? window.location.pathname : "",
      referrer: typeof document !== "undefined" ? document.referrer : "",
      submitted_at: new Date().toISOString(),
    };
    try {
      const res = await fetch("/api/inquiry", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify(payload),
      });
      if (!res.ok) throw new Error(`HTTP ${res.status}`);
      if (window.mtTrack) {
        if (data.location === "Other") {
          window.mtTrack("LeadOutOfArea", { content_category: "stoneguard_handoff" }, eventId, true);
        } else {
          window.mtTrack("Lead", {
            value: window.MT_LEAD_VALUE || 0,
            currency: "USD",
            content_category: "stone_protection",
            content_name: data.location || "",
          }, eventId);
        }
      }
      setStep(total);
    } catch (err) {
      setServerError("Couldn't send your inquiry. Try again, or call Michael at (908) 866-1919.");
    } finally {
      setSubmitting(false);
    }
  };

  return (
    <>
      <div className={"m-sheet-backdrop" + (open ? " open" : "")} onClick={close}/>
      <div className={"m-sheet" + (open ? " open" : "")} role="dialog" aria-modal="true" aria-label="Get a quote">
        <div className="m-sheet__handle" aria-hidden="true"/>
        <div className="m-sheet__head">
          <div>
            <div className="mono" style={{ fontSize: 10, letterSpacing: "0.18em", textTransform: "uppercase", color: "var(--mute)" }}>
              {step < total ? `Step ${step + 1} / ${total}` : "Submitted"}
            </div>
            <div className="serif" style={{ fontSize: 22, letterSpacing: "-0.01em", marginTop: 4 }}>
              {step < total ? M_STEPS[step].title : (isOutOfArea ? "Forwarded to StoneGuard." : "Thanks — we'll be in touch.")}
            </div>
          </div>
          <button onClick={close} aria-label="Close" style={{ padding: 8 }}>
            <svg width="20" height="20" viewBox="0 0 20 20" fill="none">
              <path d="M5 5l10 10M15 5L5 15" stroke="currentColor" strokeWidth="1.4" strokeLinecap="round"/>
            </svg>
          </button>
        </div>
        <div style={{ height: 2, background: "rgba(20,20,18,0.06)", position: "relative" }}>
          <div style={{
            position: "absolute", top: 0, left: 0, height: "100%",
            width: `${(Math.min(step + 1, total) / total) * 100}%`,
            background: "var(--maroon)", transition: "width .35s cubic-bezier(.2,.7,.2,1)",
          }}/>
        </div>
        <div className="m-sheet__body">
          {step === 0 && (
            <>
              <p className="body" style={{ marginBottom: 18 }}>We service NY, NJ, and PA directly. Anywhere else, we forward to StoneGuard.</p>
              <div style={{ display: "grid", gap: 10 }}>
                {[
                  { v: "NY",    l: "New York" },
                  { v: "NJ",    l: "New Jersey" },
                  { v: "PA",    l: "Pennsylvania" },
                  { v: "Other", l: "Somewhere else" },
                ].map(o => (
                  <OptTile key={o.v} active={data.location === o.v} onClick={() => upd("location", o.v)}>
                    <span className="serif" style={{ fontSize: 18 }}>{o.l}</span>
                  </OptTile>
                ))}
              </div>
              {isOutOfArea && (
                <div style={{
                  marginTop: 18, padding: "16px 18px",
                  borderLeft: "2px solid var(--maroon)",
                  background: "var(--cream)",
                }}>
                  <div className="mono" style={{ fontSize: 9, letterSpacing: "0.18em", textTransform: "uppercase", color: "var(--maroon)", marginBottom: 6 }}>
                    Out of service area
                  </div>
                  <p style={{ fontSize: 13, lineHeight: 1.55, color: "var(--ink-2)", margin: 0 }}>
                    We don't service this region directly. Continue anyway — we'll forward your inquiry to <strong>StoneGuard</strong>, who will connect you with your nearest installer.
                  </p>
                </div>
              )}
            </>
          )}

          {step === 1 && (
            <>
              <p className="body" style={{ marginBottom: 18 }}>What kind of stone are we protecting?</p>
              <div style={{ display: "grid", gap: 10 }}>
                {["Marble", "Quartzite", "Quartz", "Travertine", "Porcelain", "Granite", "Other"].map(s => (
                  <OptTile key={s} active={data.stone === s} onClick={() => upd("stone", s)}>{s}</OptTile>
                ))}
              </div>
              {data.stone === "Other" && (
                <input
                  type="text"
                  value={data.stoneOther}
                  onChange={e => upd("stoneOther", e.target.value)}
                  placeholder="Tell us what kind of stone"
                  autoFocus
                  style={{
                    marginTop: 12, width: "100%", padding: "12px 14px",
                    background: "var(--cream)", border: "1px solid var(--line)",
                    fontSize: 15, color: "var(--ink)",
                  }}
                />
              )}
            </>
          )}

          {step === 2 && (
            <>
              <p className="body" style={{ marginBottom: 18 }}>Which surfaces need protection? Select all that apply.</p>
              <div style={{ display: "grid", gap: 10 }}>
                {[
                  "Kitchen Countertop",
                  "Kitchen Island",
                  "Kitchen Backsplash / Waterfall",
                  "Bathroom Vanity",
                  "Table",
                  "Other",
                ].map(s => (
                  <OptTile key={s} active={data.surfaces.includes(s)} onClick={() => toggle("surfaces", s)} check>{s}</OptTile>
                ))}
              </div>
              {data.surfaces.includes("Other") && (
                <input
                  type="text"
                  value={data.surfacesOther}
                  onChange={e => upd("surfacesOther", e.target.value)}
                  placeholder="Tell us what other surfaces"
                  style={{
                    marginTop: 12, width: "100%", padding: "12px 14px",
                    background: "var(--cream)", border: "1px solid var(--line)",
                    fontSize: 15, color: "var(--ink)",
                  }}
                />
              )}

              <div style={{ marginTop: 28, paddingTop: 24, borderTop: "1px solid var(--line)" }}>
                <div style={{ display: "flex", justifyContent: "space-between", alignItems: "baseline", marginBottom: 10 }}>
                  <span className="mono" style={{ fontSize: 10, letterSpacing: "0.18em", textTransform: "uppercase", color: "var(--mute)" }}>
                    Square footage (ballpark)
                  </span>
                  <span className="serif" style={{ fontSize: 22, letterSpacing: "-0.01em" }}>
                    {data.sqftKnown ? (
                      <>
                        {data.sqft >= 200 ? "200+" : data.sqft}
                        <span style={{ fontSize: 13, color: "var(--mute)" }}> sq ft</span>
                      </>
                    ) : (
                      <span style={{ color: "var(--mute)", fontStyle: "italic", fontSize: 18 }}>Unknown</span>
                    )}
                  </span>
                </div>
                <input
                  type="range" min="5" max="200" step="5"
                  value={data.sqft}
                  onChange={e => {
                    upd("sqft", Number(e.target.value));
                    if (!data.sqftKnown) upd("sqftKnown", true);
                  }}
                  style={{
                    width: "100%", appearance: "none", WebkitAppearance: "none",
                    height: 4,
                    background: `linear-gradient(to right, var(--maroon) 0%, var(--maroon) ${data.sqftKnown ? ((data.sqft - 5) / 195) * 100 : 0}%, var(--line) ${data.sqftKnown ? ((data.sqft - 5) / 195) * 100 : 0}%, var(--line) 100%)`,
                    outline: "none", opacity: data.sqftKnown ? 1 : 0.5,
                  }}
                />
                <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginTop: 6 }}>
                  <span className="mono" style={{ fontSize: 9, letterSpacing: "0.14em", color: "var(--mute)" }}>
                    {data.sqftKnown ? "5 sq ft" : "Drag to set, or leave Unknown"}
                  </span>
                  {data.sqftKnown && (
                    <button
                      type="button"
                      onClick={() => upd("sqftKnown", false)}
                      className="mono"
                      style={{ fontSize: 9, letterSpacing: "0.12em", textTransform: "uppercase", color: "var(--maroon)", padding: 0 }}
                    >
                      Reset to unknown
                    </button>
                  )}
                </div>
                <style>{`
                  input[type="range"]::-webkit-slider-thumb {
                    appearance: none; -webkit-appearance: none;
                    width: 22px; height: 22px; border-radius: 50%;
                    background: var(--ink); cursor: grab;
                    border: 3px solid var(--bone);
                    box-shadow: 0 2px 8px rgba(0,0,0,0.15);
                  }
                  input[type="range"]::-moz-range-thumb {
                    width: 22px; height: 22px; border-radius: 50%;
                    background: var(--ink); cursor: grab;
                    border: 3px solid var(--bone);
                  }
                `}</style>
              </div>
            </>
          )}

          {step === 3 && (
            <>
              <p className="body" style={{ marginBottom: 18 }}>Helps us know if we're protecting fresh stone or fixing trouble first.</p>
              <div style={{ display: "grid", gap: 10 }}>
                {[
                  { v: "Proactive",       d: "We want peace of mind." },
                  { v: "Staining",        d: "We've had staining." },
                  { v: "Etching",         d: "We've had etching, dull spots, or rings." },
                  { v: "Newly installed", d: "We're moving in / newly installed stone." },
                  { v: "Exploring",       d: "Just exploring options / getting a quote." },
                ].map(o => (
                  <OptTile key={o.v} active={data.situation === o.v} onClick={() => upd("situation", o.v)}>
                    <span style={{ display: "block" }}>
                      <span style={{ fontWeight: 500 }}>{o.v}</span>
                      <span style={{ display: "block", fontSize: 12, color: data.situation === o.v ? "rgba(245,242,237,0.7)" : "var(--mute)", marginTop: 2 }}>{o.d}</span>
                    </span>
                  </OptTile>
                ))}
              </div>
            </>
          )}

          {step === 4 && (
            <>
              <p className="body" style={{ marginBottom: 18 }}>We're currently booking about a month out for most projects.</p>
              <div style={{ display: "grid", gap: 10 }}>
                {[
                  { v: "ASAP",       d: "As soon as possible" },
                  { v: "1–3 months", d: "In the next 1–3 months" },
                  { v: "3–6 months", d: "In 3–6 months" },
                  { v: "6 months+",  d: "Six months out or more" },
                  { v: "Planning",   d: "Just planning ahead" },
                ].map(o => (
                  <OptTile key={o.v} active={data.timeline === o.v} onClick={() => upd("timeline", o.v)}>
                    <span style={{ display: "block" }}>
                      <span className="serif" style={{ fontSize: 18 }}>{o.v}</span>
                      <span style={{ display: "block", fontSize: 12, color: data.timeline === o.v ? "rgba(245,242,237,0.7)" : "var(--mute)", marginTop: 2 }}>{o.d}</span>
                    </span>
                  </OptTile>
                ))}
              </div>
            </>
          )}

          {step === 5 && (
            <>
              <p className="body" style={{ marginBottom: 18 }}>Helps us understand what's working — and what isn't.</p>
              <div style={{ display: "grid", gap: 10 }}>
                {[
                  "Instagram / Facebook video",
                  "Google",
                  "Referred by a friend we serviced",
                  "Designer / fabricator referral",
                  "Other",
                ].map(s => (
                  <OptTile key={s} active={data.howHeard === s} onClick={() => upd("howHeard", s)}>{s}</OptTile>
                ))}
              </div>
              {data.howHeard === "Other" && (
                <input
                  type="text"
                  value={data.howHeardOther}
                  onChange={e => upd("howHeardOther", e.target.value)}
                  placeholder="How did you hear about us?"
                  autoFocus
                  style={{
                    marginTop: 12, width: "100%", padding: "12px 14px",
                    background: "var(--cream)", border: "1px solid var(--line)",
                    fontSize: 15, color: "var(--ink)",
                  }}
                />
              )}
            </>
          )}

          {step === 6 && (
            <PhotosAndMessageMobile data={data} upd={upd} />
          )}

          {step === 7 && (
            <>
              <p className="body" style={{ marginBottom: 18 }}>{ctaPromise}</p>
              <div style={{ display: "grid", gap: 12 }}>
                <Field
                  label="Your name" value={data.name}
                  onChange={v => upd("name", v)}
                  onBlur={() => blurMe("name")}
                  error={touched.name && fieldErrors.name}
                  placeholder="First and last"
                  autoComplete="name"
                />
                <Field
                  label="Email" value={data.email}
                  onChange={v => upd("email", v)}
                  onBlur={() => blurMe("email")}
                  error={touched.email && fieldErrors.email}
                  placeholder="you@example.com"
                  type="email" inputMode="email" autoComplete="email"
                />
                <Field
                  label="Phone" value={data.phone}
                  onChange={v => upd("phone", fmtPhone(v))}
                  onBlur={() => blurMe("phone")}
                  error={touched.phone && fieldErrors.phone}
                  placeholder="(908) 555-0100"
                  type="tel" inputMode="tel" autoComplete="tel"
                />

                {/* SMS consent */}
                <label style={{
                  display: "grid", gridTemplateColumns: "22px 1fr", gap: 12,
                  padding: "14px 16px",
                  background: "var(--cream)",
                  border: "1px solid var(--line)",
                  cursor: "pointer",
                  alignItems: "start",
                }}>
                  <span style={{
                    width: 18, height: 18, marginTop: 2,
                    borderRadius: 3,
                    border: "1px solid " + (data.smsConsent ? "var(--ink)" : "var(--mute)"),
                    background: data.smsConsent ? "var(--ink)" : "transparent",
                    display: "grid", placeItems: "center",
                  }}>
                    {data.smsConsent && (
                      <svg width="11" height="11" viewBox="0 0 12 12"><path d="M2 6l3 3 5-7" stroke="var(--bone)" strokeWidth="1.6" fill="none"/></svg>
                    )}
                  </span>
                  <input
                    type="checkbox" checked={data.smsConsent}
                    onChange={e => upd("smsConsent", e.target.checked)}
                    style={{ position: "absolute", opacity: 0, width: 0, height: 0 }}
                  />
                  <span style={{ fontSize: 12.5, lineHeight: 1.5, color: "var(--ink-2)" }}>
                    It's okay to text me about my project. Reply STOP to opt out. Msg &amp; data rates may apply. See <a href="/sms-terms" target="_blank" rel="noopener" style={{ color: "var(--maroon)", borderBottom: "1px solid var(--maroon)" }}>SMS Terms</a> &amp; <a href="/privacy" target="_blank" rel="noopener" style={{ color: "var(--maroon)", borderBottom: "1px solid var(--maroon)" }}>Privacy</a>.
                  </span>
                </label>

                <MobileAddressFields data={data} upd={upd} setData={setData} setTouched={setTouched} touched={touched} fieldErrors={fieldErrors} blurMe={blurMe} />

                {/* Honeypot */}
                <label
                  aria-hidden="true"
                  style={{ position: "absolute", left: "-10000px", top: "auto", width: 1, height: 1, overflow: "hidden" }}
                >
                  Website (leave blank)
                  <input
                    type="text" tabIndex={-1} autoComplete="off"
                    value={data.website}
                    onChange={e => upd("website", e.target.value)}
                  />
                </label>

                {serverError && (
                  <div role="alert" style={{
                    padding: "12px 14px",
                    background: "rgba(107,20,22,0.08)",
                    border: "1px solid rgba(107,20,22,0.3)",
                    color: "var(--maroon)",
                    fontSize: 13, lineHeight: 1.5,
                  }}>
                    {serverError}
                  </div>
                )}
              </div>
            </>
          )}

          {step === total && (
            <div style={{ textAlign: "center", padding: "20px 0" }}>
              <div style={{
                width: 56, height: 56, borderRadius: 99,
                border: "1px solid var(--maroon)", margin: "0 auto 20px",
                display: "grid", placeItems: "center", color: "var(--maroon)",
              }}>
                <svg width="22" height="22" viewBox="0 0 22 22" fill="none">
                  <path d="M4 11l5 5L18 6" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"/>
                </svg>
              </div>
              <div className="serif" style={{ fontSize: 26, letterSpacing: "-0.01em", marginBottom: 12 }}>
                {isOutOfArea ? "Forwarded to StoneGuard." : "Your inquiry is in."}
              </div>
              <p className="body">
                {isOutOfArea
                  ? "We've passed your details to StoneGuard. Expect to hear from them within a couple of business days."
                  : "We'll send back a custom protection plan and quote within 24 hours, often sooner."}
              </p>
            </div>
          )}
        </div>
        <div className="m-sheet__foot">
          {step > 0 && step < total && (
            <button className="btn btn-ghost" onClick={() => setStep(s => s - 1)}>
              <svg width="12" height="12" viewBox="0 0 12 12" fill="none"><path d="M11 6H1m0 0L6 1M1 6l5 5" stroke="currentColor" strokeWidth="1.3"/></svg>
              Back
            </button>
          )}
          {step < total - 1 && (
            <button
              className="btn btn-primary"
              style={{ flex: 1 }}
              onClick={() => setStep(s => s + 1)}
              disabled={!canAdvance}
            >
              Continue
              <svg width="12" height="12" viewBox="0 0 12 12" fill="none"><path d="M1 6h10m0 0L6 1m5 5L6 11" stroke="currentColor" strokeWidth="1.3"/></svg>
            </button>
          )}
          {step === total - 1 && (
            <button
              className="btn btn-primary"
              style={{ flex: 1, opacity: submitting ? 0.6 : 1 }}
              onClick={() => !submitting && submitInquiry()}
              disabled={submitting || !canAdvance}
            >
              {submitting ? "Sending…" : (isOutOfArea ? "Forward to StoneGuard" : "Send my inquiry")}
            </button>
          )}
          {step === total && (
            <button className="btn btn-primary" style={{ flex: 1 }} onClick={close}>Close</button>
          )}
        </div>
      </div>
    </>
  );
}

// ─────────────────────────────────────────────
// Photos + message step (mobile)
// ─────────────────────────────────────────────
function PhotosAndMessageMobile({ data, upd }) {
  const fileRef = useRef(null);
  const MAX_PHOTOS = 4;
  const MAX_SIZE_MB = 8;

  const handleFiles = async (files) => {
    const arr = Array.from(files);
    const remaining = MAX_PHOTOS - data.photos.length;
    const slice = arr.slice(0, remaining);
    const added = [];
    for (const f of slice) {
      if (!f.type.startsWith("image/")) continue;
      if (f.size > MAX_SIZE_MB * 1024 * 1024) continue;
      try {
        const dataUrl = await mFileToDataURL(f);
        added.push({ name: f.name, size: f.size, type: f.type, dataUrl });
      } catch {}
    }
    upd("photos", [...data.photos, ...added]);
  };

  const removePhoto = (i) => upd("photos", data.photos.filter((_, idx) => idx !== i));

  return (
    <>
      <p className="body" style={{ marginBottom: 18 }}>Optional — but a single photo helps us prepare a much better quote.</p>

      {/* Photo grid */}
      {data.photos.length > 0 && (
        <div style={{ display: "grid", gridTemplateColumns: "repeat(3, 1fr)", gap: 8, marginBottom: 12 }}>
          {data.photos.map((p, i) => (
            <div key={i} style={{ position: "relative", aspectRatio: "1/1", overflow: "hidden", border: "1px solid var(--line)" }}>
              <img src={p.dataUrl} alt={p.name} style={{ width: "100%", height: "100%", objectFit: "cover" }}/>
              <button
                type="button"
                onClick={() => removePhoto(i)}
                aria-label={`Remove ${p.name}`}
                style={{
                  position: "absolute", top: 4, right: 4,
                  width: 22, height: 22, borderRadius: 99,
                  background: "rgba(10,9,8,0.75)", color: "var(--bone)",
                  display: "grid", placeItems: "center", padding: 0,
                }}
              >
                <svg width="9" height="9" viewBox="0 0 12 12"><path d="M2 2l8 8M10 2L2 10" stroke="currentColor" strokeWidth="1.4"/></svg>
              </button>
            </div>
          ))}
        </div>
      )}

      {/* Upload button */}
      {data.photos.length < MAX_PHOTOS && (
        <button
          type="button"
          onClick={() => fileRef.current?.click()}
          style={{
            width: "100%", padding: "20px 16px",
            border: "1px dashed var(--line)",
            background: "var(--cream)",
            textAlign: "center",
            cursor: "pointer",
          }}
        >
          <div className="serif" style={{ fontSize: 16, color: "var(--ink)" }}>
            <span style={{ color: "var(--maroon)", borderBottom: "1px solid var(--maroon)" }}>Add photos</span>
          </div>
          <div className="mono" style={{ fontSize: 9, letterSpacing: "0.14em", textTransform: "uppercase", color: "var(--mute)", marginTop: 6 }}>
            Up to {MAX_PHOTOS} · {MAX_SIZE_MB}MB each
          </div>
          <input
            ref={fileRef}
            type="file"
            accept="image/*"
            multiple
            onChange={(e) => handleFiles(e.target.files)}
            style={{ display: "none" }}
          />
        </button>
      )}

      {/* Message */}
      <div style={{ marginTop: 22 }}>
        <span className="mono" style={{ fontSize: 10, letterSpacing: "0.18em", textTransform: "uppercase", color: "var(--mute)", marginBottom: 8, display: "block" }}>
          Message (optional)
        </span>
        <textarea
          value={data.message}
          onChange={e => upd("message", e.target.value)}
          placeholder="Designer/architect, specific stone, existing damage, scheduling…"
          rows={4}
          style={{
            width: "100%", padding: 14, fontSize: 15,
            background: "var(--cream)", border: "1px solid var(--line)",
            resize: "vertical", color: "var(--ink)",
          }}
        />
      </div>
    </>
  );
}

// ─────────────────────────────────────────────
// Address fields (mobile) — autocomplete + split city/state/zip
// ─────────────────────────────────────────────
function MobileAddressFields({ data, upd, setData, setTouched, touched, fieldErrors, blurMe }) {
  const [suggestions, setSuggestions] = useState([]);
  const [open, setOpen] = useState(false);
  const [highlight, setHighlight] = useState(-1);
  const wrapRef = useRef(null);
  const debounceRef = useRef(null);
  const sessionTokenRef = useRef(null);

  const getSessionToken = () => {
    if (!sessionTokenRef.current) {
      sessionTokenRef.current = (window.crypto && crypto.randomUUID && crypto.randomUUID()) || String(Math.random()).slice(2);
    }
    return sessionTokenRef.current;
  };

  const fetchSuggestions = async (input) => {
    if (!input || input.trim().length < 3) { setSuggestions([]); setOpen(false); return; }
    const key = window.GOOGLE_PLACES_KEY;
    if (!key) return;
    try {
      const res = await fetch("https://places.googleapis.com/v1/places:autocomplete", {
        method: "POST",
        headers: { "Content-Type": "application/json", "X-Goog-Api-Key": key },
        body: JSON.stringify({ input, sessionToken: getSessionToken(), includedRegionCodes: ["us"] }),
      });
      if (!res.ok) { console.warn("[places] autocomplete", res.status); return; }
      const json = await res.json();
      setSuggestions(json.suggestions || []);
      setOpen(true);
      setHighlight(-1);
    } catch (err) { console.warn("[places] autocomplete error", err); }
  };

  const handleSelect = async (sug) => {
    const pred = sug.placePrediction;
    if (!pred) return;
    setOpen(false); setSuggestions([]);
    const key = window.GOOGLE_PLACES_KEY;
    try {
      const url = `https://places.googleapis.com/v1/places/${encodeURIComponent(pred.placeId)}?sessionToken=${encodeURIComponent(getSessionToken())}`;
      const res = await fetch(url, {
        headers: { "X-Goog-Api-Key": key, "X-Goog-FieldMask": "addressComponents,formattedAddress" },
      });
      if (!res.ok) { console.warn("[places] details", res.status); upd("addressLine1", pred.text?.text || ""); return; }
      const place = await res.json();
      sessionTokenRef.current = null;
      const components = (place.addressComponents || []).map(c => ({
        long_name: c.longText, short_name: c.shortText, types: c.types,
      }));
      const parts = parseAddressComponentsM(components);
      setData(d => ({
        ...d,
        addressLine1: parts.line1 || d.addressLine1,
        addressLine2: parts.line2 || d.addressLine2,
        addressCity:  parts.city  || d.addressCity,
        addressState: parts.state || d.addressState,
        addressZip:   parts.zip   || d.addressZip,
        addressFormatted: place.formattedAddress || "",
      }));
      setTouched(t => ({ ...t, addressLine1: true, addressCity: true, addressState: true, addressZip: true }));
    } catch (err) {
      console.warn("[places] details error", err);
      upd("addressLine1", pred.text?.text || "");
    }
  };

  const handleChange = (v) => {
    upd("addressLine1", v);
    if (data.addressFormatted) upd("addressFormatted", "");
    clearTimeout(debounceRef.current);
    debounceRef.current = setTimeout(() => fetchSuggestions(v), 220);
  };

  useEffect(() => {
    const onDown = (e) => {
      if (wrapRef.current && !wrapRef.current.contains(e.target)) setOpen(false);
    };
    document.addEventListener("mousedown", onDown);
    return () => document.removeEventListener("mousedown", onDown);
  }, []);

  const onKeyDown = (e) => {
    if (!open || suggestions.length === 0) return;
    if (e.key === "ArrowDown") { e.preventDefault(); setHighlight(h => Math.min(h + 1, suggestions.length - 1)); }
    else if (e.key === "ArrowUp") { e.preventDefault(); setHighlight(h => Math.max(h - 1, 0)); }
    else if (e.key === "Enter" && highlight >= 0) { e.preventDefault(); handleSelect(suggestions[highlight]); }
    else if (e.key === "Escape") { setOpen(false); }
  };

  const baseInput = {
    width: "100%", padding: "12px 14px",
    background: "var(--cream)", border: "1px solid var(--line)",
    fontSize: 15, color: "var(--ink)",
  };
  const errStyle = (k) => (touched[k] && fieldErrors[k]) ? {
    borderColor: "var(--maroon)", background: "rgba(107,20,22,0.04)",
  } : null;

  return (
    <div style={{ display: "grid", gap: 8 }}>
      <div style={{ display: "flex", alignItems: "baseline", justifyContent: "space-between", marginBottom: 2 }}>
        <span className="mono" style={{ fontSize: 10, letterSpacing: "0.18em", textTransform: "uppercase", color: "var(--mute)" }}>
          Project address
        </span>
        <span className="mono" style={{ fontSize: 9, letterSpacing: "0.1em", color: "var(--maroon)" }}>Required</span>
      </div>

      <div ref={wrapRef} style={{ position: "relative" }}>
        <input
          type="text"
          value={data.addressLine1}
          onChange={(e) => handleChange(e.target.value)}
          onFocus={() => { if (suggestions.length > 0) setOpen(true); }}
          onKeyDown={onKeyDown}
          onBlur={() => blurMe("addressLine1")}
          placeholder="Start typing your address…"
          autoComplete="off"
          style={{ ...baseInput, ...(errStyle("addressLine1") || {}) }}
        />
        {open && suggestions.length > 0 && (
          <div role="listbox" style={{
            position: "absolute", top: "calc(100% + 4px)", left: 0, right: 0,
            background: "var(--bone)", border: "1px solid var(--line)",
            boxShadow: "0 16px 40px rgba(10,9,8,0.22)",
            zIndex: 10000, maxHeight: 320, overflowY: "auto",
          }}>
            {suggestions.map((sug, i) => {
              const pred = sug.placePrediction;
              if (!pred) return null;
              const main = pred.structuredFormat?.mainText?.text || pred.text?.text || "";
              const secondary = pred.structuredFormat?.secondaryText?.text || "";
              const active = highlight === i;
              return (
                <div
                  key={pred.placeId || i}
                  role="option"
                  aria-selected={active}
                  onMouseDown={(e) => { e.preventDefault(); handleSelect(sug); }}
                  onMouseEnter={() => setHighlight(i)}
                  style={{
                    padding: "12px 16px", fontSize: 14, lineHeight: 1.4, cursor: "pointer",
                    borderBottom: i < suggestions.length - 1 ? "1px solid rgba(217,210,198,0.5)" : "none",
                    background: active ? "rgba(232,212,168,0.18)" : "transparent",
                  }}
                >
                  <div style={{ fontWeight: 500, color: "var(--ink)" }}>{main}</div>
                  {secondary && <div style={{ fontSize: 12, color: "var(--mute)", marginTop: 2 }}>{secondary}</div>}
                </div>
              );
            })}
          </div>
        )}
        {touched.addressLine1 && fieldErrors.addressLine1 && (
          <div className="mono" style={{ fontSize: 10.5, color: "var(--maroon)", marginTop: 4, letterSpacing: "0.06em" }}>
            {fieldErrors.addressLine1}
          </div>
        )}
      </div>

      <input
        type="text"
        value={data.addressLine2}
        onChange={(e) => upd("addressLine2", e.target.value)}
        placeholder="Apt, suite, floor (optional)"
        autoComplete="address-line2"
        style={baseInput}
      />

      <div>
        <input
          type="text"
          value={data.addressCity}
          onChange={(e) => upd("addressCity", e.target.value)}
          onBlur={() => blurMe("addressCity")}
          placeholder="City"
          autoComplete="address-level2"
          style={{ ...baseInput, ...(errStyle("addressCity") || {}) }}
        />
        {touched.addressCity && fieldErrors.addressCity && (
          <div className="mono" style={{ fontSize: 10.5, color: "var(--maroon)", marginTop: 4, letterSpacing: "0.06em" }}>
            City required
          </div>
        )}
      </div>

      <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 8 }}>
        <div>
          <select
            value={data.addressState}
            onChange={(e) => upd("addressState", e.target.value)}
            onBlur={() => blurMe("addressState")}
            autoComplete="address-level1"
            style={{
              ...baseInput, appearance: "none",
              backgroundImage: "url(\"data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'><path d='M1 1l4 4 4-4' stroke='%236B645B' stroke-width='1.2' fill='none'/></svg>\")",
              backgroundRepeat: "no-repeat", backgroundPosition: "right 12px center",
              paddingRight: 32,
              ...(errStyle("addressState") || {}),
              color: data.addressState ? "var(--ink)" : "var(--mute)",
            }}
          >
            <option value="">State</option>
            {M_US_STATES.map(([code, name]) => (
              <option key={code} value={code}>{code} — {name}</option>
            ))}
          </select>
          {touched.addressState && fieldErrors.addressState && (
            <div className="mono" style={{ fontSize: 10.5, color: "var(--maroon)", marginTop: 4, letterSpacing: "0.06em" }}>
              State required
            </div>
          )}
        </div>
        <div>
          <input
            type="text"
            value={data.addressZip}
            onChange={(e) => upd("addressZip", e.target.value.replace(/[^\d-]/g, "").slice(0, 10))}
            onBlur={() => blurMe("addressZip")}
            placeholder="ZIP"
            inputMode="numeric"
            autoComplete="postal-code"
            style={{ ...baseInput, ...(errStyle("addressZip") || {}) }}
          />
          {touched.addressZip && fieldErrors.addressZip && (
            <div className="mono" style={{ fontSize: 10.5, color: "var(--maroon)", marginTop: 4, letterSpacing: "0.06em" }}>
              ZIP required
            </div>
          )}
        </div>
      </div>
    </div>
  );
}

function OptTile({ children, active, onClick, check = false }) {
  return (
    <button
      onClick={onClick}
      style={{
        textAlign: "left", padding: "14px 16px",
        background: active ? "var(--ink)" : "var(--cream)",
        color: active ? "var(--bone)" : "var(--ink)",
        border: "1px solid " + (active ? "var(--ink)" : "var(--line)"),
        fontSize: 14,
        display: "flex", alignItems: "center", justifyContent: "space-between",
        gap: 12,
        transition: "all .2s ease",
      }}
    >
      <span>{children}</span>
      {check && (
        <span style={{
          width: 18, height: 18,
          border: "1px solid " + (active ? "currentColor" : "var(--line)"),
          borderRadius: 3,
          display: "grid", placeItems: "center",
          background: active ? "currentColor" : "transparent",
        }}>
          {active && (
            <svg width="10" height="10" viewBox="0 0 12 12" fill="none">
              <path d="M2 6l3 3 5-7" stroke="var(--ink)" strokeWidth="1.6"/>
            </svg>
          )}
        </span>
      )}
    </button>
  );
}

function Field({ label, value, onChange, onBlur, placeholder, error, type = "text", inputMode, autoComplete }) {
  return (
    <label style={{ display: "block" }}>
      <span className="mono" style={{ fontSize: 10, letterSpacing: "0.18em", textTransform: "uppercase", color: "var(--mute)", display: "block", marginBottom: 6 }}>
        {label}
      </span>
      <input
        type={type} value={value}
        onChange={e => onChange(e.target.value)}
        onBlur={onBlur}
        placeholder={placeholder}
        inputMode={inputMode}
        autoComplete={autoComplete}
        style={{
          width: "100%", padding: "12px 14px",
          background: error ? "rgba(107,20,22,0.04)" : "var(--cream)",
          border: "1px solid " + (error ? "var(--maroon)" : "var(--line)"),
          fontSize: 15, color: "var(--ink)",
          transition: "border-color .2s, background .2s",
        }}
      />
      {error && (
        <div className="mono" style={{ fontSize: 10.5, color: "var(--maroon)", marginTop: 6, letterSpacing: "0.06em" }}>
          {error}
        </div>
      )}
    </label>
  );
}

// ─────────────────────────────────────────────
// Tweaks panel (mobile-friendly)
// ─────────────────────────────────────────────
function TweaksPanel({ tweaks, setTweaks, visible }) {
  if (!visible) return null;
  const setKey = (k, v) => {
    const next = { ...tweaks, [k]: v };
    setTweaks(next);
    window.parent.postMessage({ type: "__edit_mode_set_keys", edits: { [k]: v } }, "*");
  };
  return (
    <div style={{
      position: "fixed", left: 12, right: 12, bottom: 84, zIndex: 90,
      background: "var(--bone)", border: "1px solid var(--ink)",
      padding: 18, fontFamily: "'Inter', sans-serif",
      boxShadow: "0 30px 60px rgba(0,0,0,0.25)",
      maxHeight: "70vh", overflowY: "auto",
    }}>
      <div className="mono" style={{ fontSize: 11, letterSpacing: "0.14em", textTransform: "uppercase", color: "var(--mute)", marginBottom: 14 }}>
        Tweaks
      </div>

      <div style={{ marginBottom: 16 }}>
        <span className="mono" style={{ fontSize: 10, letterSpacing: "0.16em", textTransform: "uppercase", color: "var(--mute)", display: "block", marginBottom: 6 }}>
          Hero CTA copy
        </span>
        <input
          value={tweaks.ctaPrimary || ""}
          onChange={e => setKey("ctaPrimary", e.target.value)}
          style={{ width: "100%", padding: 10, background: "var(--cream)", border: "1px solid var(--line)", fontSize: 13 }}
        />
      </div>

      <div style={{ marginBottom: 16 }}>
        <span className="mono" style={{ fontSize: 10, letterSpacing: "0.16em", textTransform: "uppercase", color: "var(--mute)", display: "block", marginBottom: 6 }}>
          Sticky CTA bar
        </span>
        <div style={{ display: "flex", gap: 8 }}>
          {[true, false].map(v => (
            <button key={String(v)} onClick={() => setKey("stickyEnabled", v)} style={{
              flex: 1, padding: "10px 12px", fontSize: 12,
              background: tweaks.stickyEnabled === v ? "var(--ink)" : "var(--cream)",
              color: tweaks.stickyEnabled === v ? "var(--bone)" : "var(--ink)",
              border: "1px solid " + (tweaks.stickyEnabled === v ? "var(--ink)" : "var(--line)"),
            }}>{v ? "On" : "Off"}</button>
          ))}
        </div>
      </div>

      <div style={{ marginBottom: 16 }}>
        <span className="mono" style={{ fontSize: 10, letterSpacing: "0.16em", textTransform: "uppercase", color: "var(--mute)", display: "block", marginBottom: 6 }}>
          Hero variant
        </span>
        <div style={{ display: "flex", gap: 8 }}>
          {["stacked", "overlay", "editorial"].map(v => (
            <button key={v} onClick={() => setKey("heroVariant", v)} style={{
              flex: 1, padding: "10px 12px", fontSize: 11, textTransform: "capitalize",
              background: tweaks.heroVariant === v ? "var(--ink)" : "var(--cream)",
              color: tweaks.heroVariant === v ? "var(--bone)" : "var(--ink)",
              border: "1px solid " + (tweaks.heroVariant === v ? "var(--ink)" : "var(--line)"),
            }}>{v}</button>
          ))}
        </div>
      </div>
    </div>
  );
}

// ─────────────────────────────────────────────
// VideoModal — global testimonial-video popup (mobile)
// Mount once in HomeApp. Listens for the `watch-testimonial` event dispatched
// by any "Watch Testimonial" button, resolves the reviewer name → YouTube ID
// via window.MT_TESTIMONIAL_VIDEOS (config.js), then opens an autoplaying
// 16:9 embed. Close: ✕, tap outside the frame, or Escape. Body scroll locks.
// ─────────────────────────────────────────────
function VideoModal() {
  // media is either { kind:"file", src, poster } (self-hosted) or
  // { kind:"youtube", id } (embed). null = closed.
  const [media, setMedia] = useState(null);
  const [name, setName] = useState("");
  const [portrait, setPortrait] = useState(true); // testimonials are vertical Shorts by default

  useEffect(() => {
    const onWatch = (e) => {
      const detail = (e && e.detail) || {};
      const map = window.MT_TESTIMONIAL_VIDEOS || {};
      // A config entry is a string ID (Short), or an object: { id, landscape }
      // for a YouTube embed, or { src, poster, landscape } for a self-hosted file.
      const raw = (detail.src || detail.videoId)
        ? detail
        : (detail.name && map[detail.name]) || "";
      const entry = typeof raw === "string" ? { id: raw } : (raw || {});
      const fileSrc = entry.src || "";
      const id = entry.id || "";
      if (!fileSrc && !id) {
        console.warn("[VideoModal] No video configured for testimonial:", detail.name || detail);
        return;
      }
      setName(detail.name || "");
      setPortrait(!entry.landscape);
      setMedia(fileSrc
        ? { kind: "file", src: fileSrc, poster: entry.poster || "" }
        : { kind: "youtube", id });
    };
    window.addEventListener("watch-testimonial", onWatch);
    return () => window.removeEventListener("watch-testimonial", onWatch);
  }, []);

  useEffect(() => {
    if (!media) return;
    const onKey = (e) => { if (e.key === "Escape") setMedia(null); };
    document.addEventListener("keydown", onKey);
    const prevOverflow = document.body.style.overflow;
    document.body.style.overflow = "hidden";
    return () => {
      document.removeEventListener("keydown", onKey);
      document.body.style.overflow = prevOverflow;
    };
  }, [media]);

  if (!media) return null;

  const close = () => setMedia(null);
  const src = media.kind === "youtube"
    ? `https://www.youtube-nocookie.com/embed/${media.id}?autoplay=1&rel=0&modestbranding=1&playsinline=1`
    : "";

  return (
    <div
      onClick={close}
      role="dialog"
      aria-modal="true"
      aria-label={name ? `${name} testimonial video` : "Testimonial video"}
      style={{
        position: "fixed", inset: 0, zIndex: 10000,
        background: "rgba(8,7,6,0.92)", backdropFilter: "blur(8px)",
        display: "flex", alignItems: "center", justifyContent: "center",
        padding: "16px",
        animation: "mtVideoFade .28s ease both",
      }}
    >
      <button
        onClick={close}
        aria-label="Close video"
        style={{
          position: "fixed", top: 16, right: 16, zIndex: 1,
          width: 44, height: 44, borderRadius: "50%",
          border: "1px solid rgba(245,242,237,0.35)",
          background: "rgba(10,9,8,0.55)", color: "#F5F2ED",
          fontSize: 18, lineHeight: 1, cursor: "pointer",
          display: "inline-flex", alignItems: "center", justifyContent: "center",
        }}
      >✕</button>

      <div
        onClick={(e) => e.stopPropagation()}
        style={{
          position: "relative",
          // Portrait (Shorts): size by height so the vertical video fills the frame.
          // Landscape: full width and letterbox as usual.
          height: portrait ? "min(82vh, 760px)" : "auto",
          width: portrait ? "auto" : "100%",
          aspectRatio: portrait ? "9 / 16" : "16 / 9",
          maxWidth: "100%", maxHeight: "86vh",
          background: "#000", borderRadius: 6, overflow: "hidden",
          boxShadow: "0 24px 70px rgba(0,0,0,0.6)",
          animation: "mtVideoRise .4s cubic-bezier(.2,.7,.2,1) both",
        }}
      >
        {media.kind === "file" ? (
          <video
            src={media.src}
            poster={media.poster || undefined}
            title={name ? `${name} — testimonial` : "Testimonial video"}
            controls
            autoPlay
            playsInline
            preload="metadata"
            style={{ position: "absolute", inset: 0, width: "100%", height: "100%", objectFit: "cover", border: 0, background: "#000" }}
          />
        ) : (
          <iframe
            src={src}
            title={name ? `${name} — testimonial` : "Testimonial video"}
            allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
            allowFullScreen
            style={{ position: "absolute", inset: 0, width: "100%", height: "100%", border: 0 }}
          />
        )}
      </div>

      <style>{`
        @keyframes mtVideoFade { from { opacity: 0 } to { opacity: 1 } }
        @keyframes mtVideoRise { from { opacity: 0; transform: translateY(14px) scale(.98) } to { opacity: 1; transform: none } }
      `}</style>
    </div>
  );
}

// ─────────────────────────────────────────────
// Export to window for cross-file React babel scripts
// ─────────────────────────────────────────────
Object.assign(window, {
  useReveal, Reveal, WordReveal, Typewriter, LineEyebrow, Stars,
  useScrollDirection,
  Nav, MobileMenu, StickyCTA, TrustBar, FinalCTA, MobileFooter,
  SwipeCarousel, InquirySheet, TweaksPanel, VideoModal,
});
