// Shared building blocks, cleaner, with layered scroll interactivity
const { useState, useEffect, useRef, useMemo, useLayoutEffect } = React;

// ----- 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.15, rootMargin: opts.rootMargin ?? "0px 0px -80px 0px" }
    );
    io.observe(el);
    return () => io.disconnect();
  }, []);
  return [ref, shown];
}

function Reveal({ children, delay = 0, y = 22, dur = 1000, 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>
  );
}

// ----- Word reveal (serif headlines, staggered word fade + rise) -----
function WordReveal({ text, delay = 0, stagger = 70, className = "", style = {}, italicWords = [] }) {
  const [ref, shown] = useReveal();
  const words = text.split(" ");
  return (
    <span ref={ref} className={className} 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(40%)",
            opacity: shown ? 1 : 0,
            transition: `transform 1100ms cubic-bezier(.2,.7,.2,1) ${delay + i * stagger}ms, opacity 800ms ease ${delay + i * stagger}ms`,
            fontStyle: isItalic ? "italic" : "normal",
            color: isItalic ? "var(--maroon)" : "inherit",
            marginRight: "0.24em",
          }}>{w}</span>
        );
      })}
    </span>
  );
}

// ----- Typewriter cycle (types word, pauses, deletes, cycles to next) -----
function Typewriter({ words, typeSpeed = 110, deleteSpeed = 55, holdMs = 1600, startDelay = 600, className = "", style = {} }) {
  const [display, setDisplay] = useState("");
  const [wordIdx, setWordIdx] = useState(0);
  const [phase, setPhase] = useState("typing"); // typing | holding | deleting
  const [started, setStarted] = useState(false);

  useEffect(() => {
    const t = setTimeout(() => setStarted(true), startDelay);
    return () => clearTimeout(t);
  }, [startDelay]);

  useEffect(() => {
    if (!started) return;
    const current = words[wordIdx];
    let t;
    if (phase === "typing") {
      if (display.length < current.length) {
        t = setTimeout(() => setDisplay(current.slice(0, display.length + 1)), typeSpeed);
      } else {
        t = setTimeout(() => setPhase("deleting"), holdMs);
      }
    } else if (phase === "deleting") {
      if (display.length > 0) {
        t = setTimeout(() => setDisplay(current.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 className={className} 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.82em",
          marginLeft: "0.06em",
          marginBottom: "-0.08em",
          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>
  );
}

// ----- Line draw (animated underline for eyebrows) -----
function LineEyebrow({ children, style = {} }) {
  const [ref, shown] = useReveal();
  return (
    <div ref={ref} className="eyebrow" style={{ display: "flex", alignItems: "center", gap: 12, ...style }}>
      <span style={{
        display: "inline-block", height: 1, background: "currentColor",
        width: shown ? 28 : 0,
        transition: "width 900ms cubic-bezier(.2,.7,.2,1)",
        opacity: 0.7,
      }}/>
      <span style={{
        opacity: shown ? 1 : 0,
        transform: shown ? "none" : "translateX(-6px)",
        transition: "opacity .8s ease 300ms, transform .8s ease 300ms",
      }}>{children}</span>
    </div>
  );
}

// ----- Cursor follower (soft dot that becomes "view" on image hover) -----
function CursorFollower() {
  const ref = useRef(null);
  const labelRef = useRef(null);
  const [hovered, setHovered] = useState(null); // {label, color}
  useEffect(() => {
    if (matchMedia("(hover: none)").matches) return;
    let x = 0, y = 0, tx = 0, ty = 0, raf;
    const move = (e) => { tx = e.clientX; ty = e.clientY; };
    const loop = () => {
      x += (tx - x) * 0.18; y += (ty - y) * 0.18;
      if (ref.current) ref.current.style.transform = `translate3d(${x}px, ${y}px, 0) translate(-50%,-50%)`;
      raf = requestAnimationFrame(loop);
    };
    loop();
    window.addEventListener("mousemove", move);

    const onOver = (e) => {
      const t = e.target.closest("[data-cursor]");
      if (t) setHovered({ label: t.dataset.cursor, theme: t.dataset.cursorTheme || "light" });
    };
    const onOut = (e) => {
      const t = e.target.closest("[data-cursor]");
      if (t && !t.contains(e.relatedTarget)) setHovered(null);
    };
    document.addEventListener("mouseover", onOver);
    document.addEventListener("mouseout", onOut);
    return () => {
      cancelAnimationFrame(raf);
      window.removeEventListener("mousemove", move);
      document.removeEventListener("mouseover", onOver);
      document.removeEventListener("mouseout", onOut);
    };
  }, []);
  const isDark = hovered?.theme === "dark";
  return (
    <div ref={ref} style={{
      position: "fixed", top: 0, left: 0, zIndex: 999,
      pointerEvents: "none",
      width: hovered ? 84 : 10, height: hovered ? 84 : 10,
      borderRadius: 99,
      background: hovered ? (isDark ? "var(--bone)" : "var(--ink)") : "var(--ink)",
      color: hovered ? (isDark ? "var(--ink)" : "var(--bone)") : "transparent",
      mixBlendMode: hovered ? "normal" : "difference",
      display: "grid", placeItems: "center",
      fontFamily: "'JetBrains Mono', monospace", fontSize: 10, letterSpacing: "0.14em", textTransform: "uppercase",
      transition: "width .35s cubic-bezier(.2,.7,.2,1), height .35s cubic-bezier(.2,.7,.2,1), background .3s, color .3s",
      willChange: "transform, width, height",
    }}>
      {hovered?.label}
    </div>
  );
}

// ----- Horizontal pinned scroll section -----
// User scrolls vertically; content pans horizontally. Calibrated by inner width.
function PinnedHorizontal({ children, heightVh = 300, className = "", style = {} }) {
  const wrapRef = useRef(null);
  const trackRef = useRef(null);
  const [x, setX] = useState(0);
  useEffect(() => {
    let raf;
    const onScroll = () => {
      cancelAnimationFrame(raf);
      raf = requestAnimationFrame(() => {
        const wrap = wrapRef.current; const track = trackRef.current;
        if (!wrap || !track) return;
        const rect = wrap.getBoundingClientRect();
        const vh = window.innerHeight;
        const total = wrap.offsetHeight - vh; // scroll distance
        const progress = Math.max(0, Math.min(1, -rect.top / total));
        const maxX = track.scrollWidth - window.innerWidth;
        setX(-progress * maxX);
      });
    };
    onScroll();
    window.addEventListener("scroll", onScroll, { passive: true });
    window.addEventListener("resize", onScroll);
    return () => { window.removeEventListener("scroll", onScroll); window.removeEventListener("resize", onScroll); cancelAnimationFrame(raf); };
  }, []);
  return (
    <div ref={wrapRef} className={className} style={{ height: `${heightVh}vh`, position: "relative", ...style }}>
      <div style={{ position: "sticky", top: 0, height: "100vh", overflow: "hidden" }}>
        <div ref={trackRef} style={{ height: "100%", transform: `translate3d(${x}px, 0, 0)`, willChange: "transform", display: "flex", alignItems: "center" }}>
          {children}
        </div>
      </div>
    </div>
  );
}

// ----- Nav -----
function Nav({ onInquire }) {
  const [scrolled, setScrolled] = useState(false);
  useEffect(() => {
    const onScroll = () => setScrolled(window.scrollY > 40);
    window.addEventListener("scroll", onScroll, { passive: true });
    return () => window.removeEventListener("scroll", onScroll);
  }, []);
  return (
    <header style={{
      position: "fixed", top: 0, left: 0, right: 0, zIndex: 50,
      backdropFilter: "blur(20px) saturate(1.4)",
      WebkitBackdropFilter: "blur(20px) saturate(1.4)",
      background: scrolled ? "rgba(245,242,237,0.72)" : "rgba(245,242,237,0.55)",
      borderBottom: scrolled ? "1px solid rgba(217,210,198,0.6)" : "1px solid rgba(217,210,198,0.25)",
      boxShadow: scrolled ? "0 1px 24px rgba(10,9,8,0.04)" : "none",
      color: "var(--ink)",
      transition: "all .4s ease",
    }}>
      <div className="container" style={{
        display: "flex", alignItems: "center", justifyContent: "space-between",
        height: 76,
      }}>
        <a href="index.html" style={{ display: "flex", alignItems: "center", gap: 12 }}>
          <img src="assets/logo.png" alt="M&T Surface Protectors" style={{ height: 44, width: "auto", display: "block" }} />
          <span className="serif" style={{ fontSize: 19, letterSpacing: "-0.01em", lineHeight: 1 }}>
            M<span style={{ fontSize: 13, verticalAlign: "super", margin: "0 2px" }}>&amp;</span>T Surface Protectors
          </span>
        </a>
        <nav style={{ display: "flex", gap: 36, alignItems: "center" }} className="nav-links">
          {[
            { label: "Work",     href: "work.html" },
            { label: "Services", href: "services.html" },
            { label: "Reviews",  href: "index.html#reviews" },
            { label: "Learn",    href: "learn.html" },
            { label: "FAQ",      href: "faq.html" },
          ].map(l => (
            <a key={l.label} href={l.href} style={{ fontSize: 13, letterSpacing: "0.02em", opacity: 0.85 }}>{l.label}</a>
          ))}
          <button onClick={onInquire} className="btn btn-primary" style={{ padding: "12px 22px", fontSize: 13 }}>
            Get My Stone Protected
            <svg width="12" height="12" viewBox="0 0 12 12" fill="none"><path d="M1 6h10m0 0L6 1m5 5L6 11" stroke="currentColor" strokeWidth="1.2"/></svg>
          </button>
        </nav>
      </div>
      <style>{`@media (max-width: 860px){ .nav-links a { display: none; } }`}</style>
    </header>
  );
}

// ----- Hero: editorial split, bone background with large serif statement + photo inset -----
function Hero({ onInquire, ctaPrimary, ctaPromise }) {
  return (
    <section style={{
      paddingTop: 160, paddingBottom: 120,
      position: "relative",
      background: "var(--bone)",
    }}>
      <div className="container">
        <div style={{
          display: "grid",
          gridTemplateColumns: "1.4fr 1fr",
          gap: 80, alignItems: "end",
        }} className="hero-grid">
          <div>
            <LineEyebrow style={{ marginBottom: 32 }}>
              Certified StoneGuard® Specialists · NY · NJ · PA
            </LineEyebrow>
            <h1 className="serif" style={{
              fontSize: "clamp(44px, 5.8vw, 88px)",
              lineHeight: 1.02,
              letterSpacing: "-0.02em",
              marginBottom: 36,
              whiteSpace: "nowrap",
            }}>
              <span style={{ display: "block" }}><WordReveal text="Stone is beautiful." /></span>
              <span style={{ display: "block" }}>
                <WordReveal text="It's also" delay={260} />
                {" "}
                <span style={{ color: "var(--maroon)" }}>
                  <Typewriter
                    words={["vulnerable.", "delicate.", "fragile."]}
                    startDelay={1400}
                  />
                </span>
              </span>
            </h1>
            <Reveal delay={700}>
              <p style={{
                fontSize: 18, lineHeight: 1.55, color: "var(--ink-2)",
                maxWidth: 520, marginBottom: 44,
              }}>
                {ctaPromise}
              </p>
            </Reveal>
            <Reveal delay={820}>
              <div style={{ display: "flex", gap: 16, flexWrap: "wrap", alignItems: "center" }}>
                <button onClick={onInquire} className="btn btn-primary">
                  {ctaPrimary}
                  <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>
                <a href="#work" className="btn btn-ghost">See the work</a>
              </div>
            </Reveal>
            <Reveal delay={960}>
              <div style={{
                display: "flex", alignItems: "center", gap: 12,
                marginTop: 28,
                color: "var(--ink-2)", fontSize: 13,
                fontFamily: "'JetBrains Mono', monospace",
                letterSpacing: "0.08em",
                textTransform: "uppercase",
              }}>
                <span style={{
                  width: 8, height: 8, borderRadius: 99,
                  background: "var(--maroon)",
                  boxShadow: "0 0 0 0 var(--maroon)",
                  animation: "hero-dot-pulse 2.2s cubic-bezier(.4,.0,.6,1) infinite",
                }}/>
                Booking one month out
              </div>
              <style>{`
                @keyframes hero-dot-pulse {
                  0%, 100% { opacity: 0.45; box-shadow: 0 0 0 0 rgba(139, 115, 85, 0); }
                  50% { opacity: 1; box-shadow: 0 0 0 6px rgba(139, 115, 85, 0.18); }
                }
              `}</style>
            </Reveal>
          </div>

          <Reveal delay={320} y={20}>
            <div style={{ position: "relative" }}>
              <div style={{
                width: "100%", aspectRatio: "4/5", display: "block",
                position: "relative", overflow: "hidden",
                background: "#0f0e0d",
                boxShadow: "0 30px 60px -20px rgba(0,0,0,0.3)",
              }}>
                <img
                  src="assets/cover-stone-beautiful.png"
                  alt="Honed black stone countertop in a dark kitchen"
                  style={{
                    position: "absolute", inset: 0,
                    width: "100%", height: "100%",
                    objectFit: "cover",
                    display: "block",
                  }}
                />
                <div style={{
                  position: "absolute", inset: 0,
                  background: "rgba(0,0,0,0.13)",
                  pointerEvents: "none",
                }} />
              </div>
              <div className="hero-quote-card" style={{
                position: "absolute", right: -24, bottom: -32,
                background: "var(--bone)",
                border: "1px solid var(--line)",
                padding: "20px 24px",
                width: 280,
                boxShadow: "0 20px 40px -12px rgba(0,0,0,0.12)",
              }}>
                <div className="mono" style={{ fontSize: 10, color: "var(--mute)", marginBottom: 10, letterSpacing: "0.14em", textTransform: "uppercase" }}>
                  Shown: unprotected stone
                </div>
                <div className="serif" style={{ fontSize: 20, lineHeight: 1.28, letterSpacing: "-0.01em" }}>
                  Porous stone etches, stains, and dulls, like this countertop.
                </div>
              </div>
            </div>
          </Reveal>
        </div>
      </div>
      <style>{`
        @media (max-width: 860px){
          .hero-grid{ grid-template-columns: 1fr !important; gap: 60px !important; }
          .hero-quote-card{ right: 0 !important; bottom: -20px !important; width: auto !important; max-width: 280px; }
        }
      `}</style>
    </section>
  );
}

function Stars({ n = 5 }) {
  return (
    <div style={{ display: "inline-flex", gap: 2 }}>
      {Array.from({ length: n }).map((_, i) => (
        <svg key={i} width="12" height="12" 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>
      ))}
    </div>
  );
}

// ----- Trust Bar: big number + single star, editorial -----
// ----- Trust Bar: slow-scrolling proof ticker, uniform rhythm -----
function TrustBar() {
  const items = [
    { label: "Homes Protected",   value: "Thousands" },
    { label: "Google Rating",     value: "5.0", star: true },
    { label: "Houzz Rating",      value: "5.0", star: true },
    { label: "Warranty",          value: "Lifetime" },
    { label: "Service Area",      value: "NY · NJ · PA" },
  ];

  // Duplicate for seamless loop
  const loop = [...items, ...items];

  const Star = () => (
    <svg width="16" height="16" viewBox="0 0 11 11" fill="currentColor" style={{ marginLeft: 2, flexShrink: 0 }}>
      <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>
  );

  return (
    <section style={{
      borderTop: "1px solid var(--line)",
      borderBottom: "1px solid var(--line)",
      background: "var(--cream)",
      overflow: "hidden",
      position: "relative",
    }}>
      <div className="tb-viewport">
        <div className="tb-track">
          {loop.map((it, i) => (
            <div key={i} className="tb-item">
              <div className="mono tb-label">{it.label}</div>
              <div className="serif tb-value">
                {it.value}
                {it.star && <Star />}
              </div>
            </div>
          ))}
        </div>
      </div>

      <style>{`
        .tb-viewport {
          mask-image: linear-gradient(90deg, transparent 0, #000 80px, #000 calc(100% - 80px), transparent 100%);
          -webkit-mask-image: linear-gradient(90deg, transparent 0, #000 80px, #000 calc(100% - 80px), transparent 100%);
        }
        .tb-track {
          display: flex; align-items: center;
          padding: 36px 0;
          width: max-content;
          animation: tb-marquee 60s linear infinite;
        }
        .tb-viewport:hover .tb-track { animation-play-state: paused; }
        @keyframes tb-marquee {
          0% { transform: translateX(0); }
          100% { transform: translateX(-50%); }
        }
        .tb-item {
          display: inline-flex;
          align-items: baseline;
          gap: 18px;
          padding: 0 56px;
          white-space: nowrap;
          flex-shrink: 0;
          border-right: 1px solid var(--line);
        }
        .tb-item:last-child { border-right: none; }
        .tb-label {
          font-size: 10px;
          letter-spacing: 0.22em;
          text-transform: uppercase;
          color: var(--mute);
        }
        .tb-value {
          font-size: 28px;
          letter-spacing: -0.02em;
          color: var(--ink);
          display: inline-flex;
          align-items: center;
        }
        @media (max-width: 760px){
          .tb-track { animation-duration: 55s; padding: 28px 0; }
          .tb-item { padding: 0 36px; gap: 14px; }
          .tb-value { font-size: 24px; }
        }
      `}</style>
    </section>
  );
}

// ----- Chapter bridge: cinematic quote panel between sections -----
function ChapterBridge({ quote, author, role, img, alt }) {
  const ref = useRef(null);
  const [p, setP] = useState(0);
  useEffect(() => {
    let raf;
    const on = () => {
      cancelAnimationFrame(raf);
      raf = requestAnimationFrame(() => {
        const el = ref.current; if (!el) return;
        const r = el.getBoundingClientRect();
        const vh = window.innerHeight;
        // progress: 0 when top of section enters bottom of viewport, 1 when bottom of section exits top
        const prog = Math.max(0, Math.min(1, (vh - r.top) / (vh + r.height)));
        setP(prog);
      });
    };
    on();
    window.addEventListener("scroll", on, { passive: true });
    return () => { window.removeEventListener("scroll", on); cancelAnimationFrame(raf); };
  }, []);

  // Quote peaks at middle, fades in/out at edges
  const quoteOpacity = Math.sin(Math.PI * Math.max(0, Math.min(1, (p - 0.15) / 0.7)));
  const imgY = (0.5 - p) * 120;
  const imgScale = 1.1 + p * 0.08;
  const bandOpacity = Math.min(1, p * 3);

  return (
    <section ref={ref} style={{
      background: "var(--black)", color: "var(--bone)",
      position: "relative", overflow: "hidden",
      padding: "180px 0",
    }}>
      {/* subtle parallax image */}
      <div style={{
        position: "absolute", inset: "-10% 0",
        opacity: 0.22,
        transform: `translate3d(0, ${imgY}px, 0) scale(${imgScale})`,
        transition: "transform .15s linear",
        willChange: "transform",
      }}>
        <img src={img} alt={alt} style={{
          width: "100%", height: "100%", objectFit: "cover",
          filter: "grayscale(0.3) brightness(0.7)",
        }}/>
      </div>
      {/* vignette */}
      <div style={{
        position: "absolute", inset: 0,
        background: "radial-gradient(ellipse at center, transparent 0%, rgba(10,9,8,0.6) 70%, var(--black) 100%)",
      }}/>

      <div className="container" style={{ position: "relative", textAlign: "center" }}>
        <div className="mono" style={{
          fontSize: 11, letterSpacing: "0.24em", textTransform: "uppercase",
          color: "rgba(245,242,237,0.5)",
          marginBottom: 48,
          opacity: bandOpacity,
          transition: "opacity .4s",
        }}>
          <span style={{ display: "inline-block", width: 40, height: 1, background: "currentColor", verticalAlign: "middle", marginRight: 16, opacity: 0.6 }}/>
          In their words
          <span style={{ display: "inline-block", width: 40, height: 1, background: "currentColor", verticalAlign: "middle", marginLeft: 16, opacity: 0.6 }}/>
        </div>

        <blockquote className="serif" style={{
          fontSize: "clamp(40px, 5.5vw, 88px)",
          lineHeight: 1.08,
          letterSpacing: "-0.022em",
          maxWidth: 1200, margin: "0 auto",
          opacity: quoteOpacity,
          transform: `translateY(${(1 - quoteOpacity) * 24}px)`,
          transition: "opacity .2s linear, transform .2s linear",
        }}>
          <span style={{ color: "var(--maroon)", display: "inline-block", transform: "translateY(0.1em)" }}>"</span>{quote}<span style={{ color: "var(--maroon)", display: "inline-block", transform: "translateY(0.1em)" }}>"</span>
        </blockquote>

        <div style={{
          marginTop: 56,
          opacity: quoteOpacity,
          transition: "opacity .3s linear",
        }}>
          <div className="serif" style={{ fontSize: 22, letterSpacing: "-0.01em" }}>{author}</div>
          <div className="mono" style={{ fontSize: 11, letterSpacing: "0.16em", textTransform: "uppercase", color: "rgba(245,242,237,0.5)", marginTop: 8 }}>
            {role}
          </div>
        </div>
      </div>
    </section>
  );
}

// Unused but kept for safety
function StonePlaceholder({ ratio = "4/5", style = {} }) { return <div style={{ aspectRatio: ratio, background: "var(--bone-2)", ...style }}/>; }

// ----- VideoModal: global testimonial-video popup -----
// Mount once near the app root. Listens for the `watch-testimonial` event
// dispatched by any "Watch Testimonial" button:
//   window.dispatchEvent(new CustomEvent("watch-testimonial",
//                          { detail: { name: "Eleonora Srugo" } }));
// It resolves the name → YouTube ID via window.MT_TESTIMONIAL_VIDEOS (see
// config.js), then opens an autoplaying 16:9 YouTube embed in a dark overlay.
// Close: ✕ button, click outside the frame, or Escape. Body scroll locks
// while open. You can also pass { videoId } directly to bypass the lookup.
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.9)", backdropFilter: "blur(10px)",
        display: "flex", alignItems: "center", justifyContent: "center",
        padding: "max(20px, 4vh) max(20px, 4vw)",
        animation: "mtVideoFade .28s ease both",
      }}
    >
      <button
        onClick={close}
        aria-label="Close video"
        style={{
          position: "fixed", top: 24, right: 28, zIndex: 1,
          width: 48, height: 48, borderRadius: "50%",
          border: "1px solid rgba(245,242,237,0.35)",
          background: "rgba(10,9,8,0.5)", color: "#F5F2ED",
          fontSize: 20, lineHeight: 1, cursor: "pointer",
          display: "inline-flex", alignItems: "center", justifyContent: "center",
          transition: "background .25s ease, border-color .25s ease",
        }}
        onMouseEnter={(e) => { e.currentTarget.style.background = "rgba(245,242,237,0.15)"; e.currentTarget.style.borderColor = "rgba(245,242,237,0.7)"; }}
        onMouseLeave={(e) => { e.currentTarget.style.background = "rgba(10,9,8,0.5)"; e.currentTarget.style.borderColor = "rgba(245,242,237,0.35)"; }}
      >✕</button>

      <div
        onClick={(e) => e.stopPropagation()}
        style={{
          position: "relative",
          // Portrait (Shorts): size by height so the vertical video fills the frame.
          // Landscape: cap width and letterbox as usual.
          height: portrait ? "min(88vh, 880px)" : "auto",
          width: portrait ? "auto" : "min(1120px, 100%)",
          aspectRatio: portrait ? "9 / 16" : "16 / 9",
          maxWidth: "100%", maxHeight: "92vh",
          background: "#000", borderRadius: 6, overflow: "hidden",
          boxShadow: "0 40px 120px rgba(0,0,0,0.6)",
          animation: "mtVideoRise .42s 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(18px) scale(.98) } to { opacity: 1; transform: none } }
      `}</style>
    </div>
  );
}

Object.assign(window, { Nav, Hero, Stars, TrustBar, Reveal, WordReveal, Typewriter, LineEyebrow, PinnedHorizontal, ChapterBridge, StonePlaceholder, useReveal, VideoModal });
