const { useState, useRef, useEffect, Fragment } = React;

// ── Contact link helpers ──────────────────────────────────────────
// Single source for tel:/maps/instagram hrefs — tel: URIs must not carry spaces.
const telHref  = (n) => "tel:" + n.replace(/[^\d+]/g, "");
const mapsHref = (c) => "https://www.google.com/maps/search/?api=1&query=" + encodeURIComponent(c.mapQuery);
const igHref   = (c) => "https://instagram.com/" + c.instagram;

// Newsletter + contact form destination. Paste the Formspree (or equivalent)
// endpoint here; until then both forms fall back to a prefilled mailto:.
const FORM_ENDPOINT = "";

const nn = (i) => String(i + 1).padStart(2, "0");   // 1 -> "01"

// Photo sets are generated by build_media.py; media.js carries their counts.
// Files are zero-padded to three digits, so 1 -> "001.jpg".
const mediaList = (key) => {
  const m = (window.VTX_MEDIA || {})[key];
  if (!m) return [];
  return Array.from({ length: m.n }, (_, i) => m.dir + "/" + String(i + 1).padStart(3, "0") + ".jpg");
};

// ── useInView ─────────────────────────────────────────────────────
// Adds `is-in` to a section; everything inside animates off that one class,
// so a section costs one observer rather than one per element.
function useInView(ref, { threshold = 0.16 } = {}) {
  const [inView, setInView] = useState(false);
  useEffect(() => {
    const el = ref.current;
    if (!el) return;
    // No IntersectionObserver: show the content rather than leaving it hidden forever.
    if (typeof IntersectionObserver === "undefined") { setInView(true); return; }
    const io = new IntersectionObserver(([e]) => {
      if (e.isIntersecting) { setInView(true); io.disconnect(); }
    }, { threshold, rootMargin: "0px 0px -6% 0px" });
    io.observe(el);
    return () => io.disconnect();
  }, [ref, threshold]);
  return inView;
}

function Reveal({ as: Tag = "div", className = "", threshold, children, ...rest }) {
  const ref = useRef(null);
  const inView = useInView(ref, threshold ? { threshold } : undefined);
  return (
    <Tag ref={ref} className={(inView ? "is-in " : "") + className} {...rest}>
      {children}
    </Tag>
  );
}

// ── MaskTitle ─────────────────────────────────────────────────────
// Splits on spaces and lifts each word out of its own clipped box.
// Greek splits on spaces the same as Latin, so no special casing.
function MaskTitle({ text, as: Tag = "h2", className = "", delay = 0, step = 52 }) {
  const words = String(text || "").split(" ");
  return (
    <Tag className={className}>
      {words.map((w, i) => (
        <Fragment key={i}>
          <span className="mask"><i style={{ "--d": (delay + i * step) + "ms" }}>{w}</i></span>
          {i < words.length - 1 ? " " : null}
        </Fragment>
      ))}
    </Tag>
  );
}

// ── Btn ───────────────────────────────────────────────────────────
function Btn({ className = "", onClick, children, type, ...rest }) {
  const ref = useRef(null);
  const fire = (e) => {
    const btn = ref.current;
    if (!btn) return;
    const r = btn.getBoundingClientRect();
    const span = document.createElement("span");
    span.className = "ripple";
    span.style.left = (e.clientX - r.left) + "px";
    span.style.top  = (e.clientY - r.top)  + "px";
    btn.appendChild(span);
    span.addEventListener("animationend", () => span.remove(), { once: true });
  };
  return (
    <button ref={ref} type={type || "button"} className={"btn " + className}
            onClick={(e) => { fire(e); onClick && onClick(e); }} {...rest}>
      <span>{children}</span>
    </button>
  );
}

// ── Icon ──────────────────────────────────────────────────────────
function Icon({ name, size = 17, className = "" }) {
  const paths = {
    hammer:   "M14 3l7 7-2.5 2.5-7-7L14 3z M11.5 5.5L3 14v4a3 3 0 003 3h1l8.5-8.5",
    flame:    "M12 22a7 7 0 007-7c0-5-4-6-4-11 0 0-3 1.5-3 5 0-2-2-3-2-3s-1 2.5-3 5a7.5 7.5 0 00-1 4 7 7 0 007 7z",
    calendar: "M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z",
    users:    "M17 20h5v-2a3 3 0 00-5.36-1.86M17 20H7m10 0v-2c0-.66-.13-1.28-.36-1.86M7 20H2v-2a3 3 0 015.36-1.86M7 20v-2c0-.66.13-1.28.36-1.86m0 0a5 5 0 019.28 0M15 7a3 3 0 11-6 0 3 3 0 016 0z",
    check:    "M20 6L9 17l-5-5",
    home:     "M3 10.5L12 3l9 7.5M5 9.5V20a1 1 0 001 1h12a1 1 0 001-1V9.5",
    book:     "M4 4.5A2.5 2.5 0 016.5 2H20v15H6.5A2.5 2.5 0 004 19.5zM4 19.5A2.5 2.5 0 016.5 17H20v5H6.5A2.5 2.5 0 014 19.5z",
    instagram:"M7 2h10a5 5 0 015 5v10a5 5 0 01-5 5H7a5 5 0 01-5-5V7a5 5 0 015-5z M16 11.37a4 4 0 11-7.91 1.17A4 4 0 0116 11.37z M17.5 6.5h.01",
    facebook: "M18 2h-3a5 5 0 00-5 5v3H7v4h3v8h4v-8h3l1-4h-4V7a1 1 0 011-1h3z",
    phone:    "M22 16.9v3a2 2 0 01-2.18 2 19.8 19.8 0 01-8.63-3.07 19.5 19.5 0 01-6-6A19.8 19.8 0 012.12 4.2 2 2 0 014.1 2h3a2 2 0 012 1.72c.13.96.36 1.9.7 2.81a2 2 0 01-.45 2.11L8.1 9.9a16 16 0 006 6l1.26-1.26a2 2 0 012.11-.45c.9.34 1.85.57 2.81.7A2 2 0 0122 16.9z",
    mail:     "M4 4h16a2 2 0 012 2v12a2 2 0 01-2 2H4a2 2 0 01-2-2V6a2 2 0 012-2z M22 6l-10 7L2 6",
    pin:      "M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0118 0zM12 13a3 3 0 100-6 3 3 0 000 6z",
  };
  const d = paths[name] || paths.check;
  return (
    <svg className={"ico " + className} width={size} height={size} viewBox="0 0 24 24" fill="none"
         stroke="currentColor" strokeWidth="1.3" strokeLinecap="round" strokeLinejoin="round"
         aria-hidden="true">
      <path d={d} />
    </svg>
  );
}

const Arw = () => <span className="arw" aria-hidden="true">→</span>;

// ── LangSwitch ────────────────────────────────────────────────────
function LangSwitch({ lang, setLang }) {
  return (
    <div className="lang">
      <button className={lang === "el" ? "active" : ""} onClick={() => setLang("el")}>EL</button>
      <span className="sep">/</span>
      <button className={lang === "en" ? "active" : ""} onClick={() => setLang("en")}>EN</button>
    </div>
  );
}

// ── TopNav ────────────────────────────────────────────────────────
// Transparent over the hero photograph, frosted bone once past it. The bar is
// never a filled metal colour — it is either the photograph or the page's paper.
function TopNav({ page, setPage, lang, setLang, t }) {
  const [open, setOpen] = useState(false);
  const [np, setNp] = useState(page === "home" ? 0 : 1);

  const items = [
    { id: "home",     label: t.nav.home },
    { id: "seminars", label: t.nav.seminars },
    { id: "gallery",  label: t.nav.gallery },
    { id: "about",    label: t.nav.about },
    { id: "contact",  label: t.nav.contact }
  ];
  const go = (id) => { setPage(id); setOpen(false); };

  useEffect(() => {
    const onScroll = () => {
      const hero = document.querySelector(".hero");
      if (!hero) { setNp(1); return; }
      setNp(Math.min(window.scrollY / Math.max(hero.offsetHeight - 90, 1), 1));
    };
    onScroll();
    window.addEventListener("scroll", onScroll, { passive: true });
    window.addEventListener("resize", onScroll);
    return () => { window.removeEventListener("scroll", onScroll); window.removeEventListener("resize", onScroll); };
  }, [page]);

  // Body scroll lock while the drawer is open.
  useEffect(() => {
    document.body.style.overflow = open ? "hidden" : "";
    return () => { document.body.style.overflow = ""; };
  }, [open]);

  const over = np < 0.6 && page === "home" && !open;

  return (
    <Fragment>
      <header className={"nav " + (over ? "nav--over" : "nav--rest") + (np > 0.6 ? " nav--scrolled" : "")}>
        <div className="nav-in">
          {/* Wordmark, not an image: the studio has no logo of its own — the only
              mark on its staging site is stock clipart from the WordPress theme. */}
          <div className="nav-brand" onClick={() => go("home")}>
            <span className="nav-brand-word">Vortex Arts</span>
            <span className="nav-brand-est">Est. 2005</span>
          </div>

          <nav className="nav-links">
            {items.map(i => (
              <a key={i.id} className={page === i.id ? "active" : ""} onClick={() => go(i.id)}>{i.label}</a>
            ))}
          </nav>

          <div className="nav-right">
            <LangSwitch lang={lang} setLang={setLang} />
            <Btn className="btn-sm" onClick={() => go("contact")}>{t.nav.book}</Btn>
            <button className={"burger" + (open ? " open" : "")} onClick={() => setOpen(o => !o)}
                    aria-label="Menu" aria-expanded={open}>
              <span /><span /><span />
            </button>
          </div>
        </div>
      </header>

      {open && (
        <div className="drawer">
          <nav className="drawer-links">
            {items.map((i, k) => (
              <a key={i.id} className={page === i.id ? "active" : ""} onClick={() => go(i.id)}
                 style={{ animationDelay: (80 + k * 65) + "ms" }}>
                <span className="num">{nn(k)}</span>{i.label}
              </a>
            ))}
          </nav>
          <div className="drawer-foot">
            <LangSwitch lang={lang} setLang={setLang} />
            <Btn className="btn-light btn-sm" onClick={() => go("contact")}>{t.nav.book}</Btn>
          </div>
        </div>
      )}
    </Fragment>
  );
}

// ── ContactDetails — the one place contact links are built ────────
function ContactDetails({ t, showAddress = true }) {
  const c = t.contact;
  return (
    <div className="info-list">
      {c.phones.map(p => (
        <a key={p.n} href={telHref(p.n)}>
          <Icon name="phone" size={14} />{p.n}<span style={{ opacity: .5 }}>· {p.l}</span>
        </a>
      ))}
      <a href={"mailto:" + c.email}><Icon name="mail" size={14} />{c.email}</a>
      {showAddress && (
        <a href={mapsHref(c)} target="_blank" rel="noopener noreferrer">
          <Icon name="pin" size={14} />{c.addr.join(", ")}
        </a>
      )}
      <a href={igHref(c)} target="_blank" rel="noopener noreferrer"><Icon name="instagram" size={14} />@{c.instagram}</a>
      <a href={c.facebook} target="_blank" rel="noopener noreferrer"><Icon name="facebook" size={14} />Facebook</a>
    </div>
  );
}

// ── ContactModal ──────────────────────────────────────────────────
function ContactModal({ t, onClose }) {
  useEffect(() => {
    const onKey = (e) => { if (e.key === "Escape") onClose(); };
    document.addEventListener("keydown", onKey);
    document.body.style.overflow = "hidden";
    return () => { document.removeEventListener("keydown", onKey); document.body.style.overflow = ""; };
  }, [onClose]);

  return ReactDOM.createPortal(
    <div className="modal-back" onClick={onClose}>
      <div className="modal" role="dialog" aria-modal="true" onClick={(e) => e.stopPropagation()}>
        <button className="modal-close" aria-label={t.book.close} onClick={onClose}>×</button>
        <span className="label">{t.book.eb}</span>
        <h3>{t.book.title}</h3>
        <p>{t.book.lede}</p>
        <ContactDetails t={t} />
      </div>
    </div>,
    document.body
  );
}

// ── Hero — full bleed, headline set over the plate ────────────────
function Hero({ t, setPage }) {
  const h = t.home;
  const [ready, setReady] = useState(false);
  const [booking, setBooking] = useState(false);
  useEffect(() => { const id = setTimeout(() => setReady(true), 60); return () => clearTimeout(id); }, []);

  const meta = [
    { k: t.footer.findEb,   v: t.contact.addr[1] },
    { k: t.contact.hoursEb, v: t.contact.hours[1] }
  ];

  return (
    <section className={"hero" + (ready ? " ready is-in" : "")}>
      <div className="hero-media">
        <img src="/assets/photos/hero.jpg" alt="Σφυρήλατα δαχτυλίδια από μπρούντζο, φτιαγμένα στο εργαστήριο" />
      </div>

      <div className="wrap hero-body">
        <span className="hero-eb label"
              style={{ opacity: ready ? 1 : 0, transition: "opacity .9s ease .15s" }}>{h.eyebrow}</span>
        <MaskTitle as="h1" className="hero-title" text={h.hero} delay={220} step={70} />
        <p className="hero-lede rise" style={{ "--d": "780ms" }}>{h.lede}</p>
        <div className="hero-actions rise" style={{ "--d": "900ms" }}>
          <Btn className="btn-light" onClick={() => setBooking(true)}>{h.ctaPrimary}</Btn>
          <Btn className="btn-light" onClick={() => setPage("seminars")}>{h.ctaContact}</Btn>
        </div>
      </div>

      <div className="wrap">
        <div className="hero-meta rise" style={{ "--d": "1040ms" }}>
          {meta.map((m, i) => (
            <div key={i} className="hero-meta-item">
              <span className="k label">{m.k}</span>
              <span className="v">{m.v}</span>
            </div>
          ))}
          <div className="hero-scroll"><i /><span>Scroll</span></div>
        </div>
      </div>

      {booking && <ContactModal t={t} onClose={() => setBooking(false)} />}
    </section>
  );
}

// ── Figures — one compact baseline band, numeral and label side by side ──
// Not a grid of bordered cells: those were mostly empty space.
function Figure({ x, i }) {
  const ref = useRef(null);
  const inView = useInView(ref, { threshold: 0.5 });
  const [n, setN] = useState(0);
  const target = parseFloat(String(x.n).replace(/[^\d.]/g, "")) || 0;

  useEffect(() => {
    if (!inView || target === 0) return;
    const dur = 1400, t0 = performance.now();
    let raf;
    const tick = (now) => {
      const p = Math.min((now - t0) / dur, 1);
      setN(Math.round(target * (1 - Math.pow(1 - p, 3))));
      if (p < 1) raf = requestAnimationFrame(tick);
    };
    raf = requestAnimationFrame(tick);
    return () => cancelAnimationFrame(raf);
  }, [inView, target]);

  return (
    <div ref={ref} className={"fig rise " + (inView ? "is-in" : "")} style={{ "--d": (i * 110) + "ms" }}>
      <span className="fig-n">{n}</span>
      <span className="fig-l">{x.l}</span>
    </div>
  );
}

function IndexRow({ t }) {
  const s = t.stats;
  const figs = [s.years, s.group, s.materials];
  return (
    <section className="figs">
      <div className="wrap figs-row">
        {figs.map((x, i) => (
          <Fragment key={i}>
            {i > 0 && <span className="fig-sep" aria-hidden="true" />}
            <Figure x={x} i={i} />
          </Fragment>
        ))}
      </div>
    </section>
  );
}

// ── Marquee ───────────────────────────────────────────────────────
function Marquee({ items }) {
  // Two identical halves; the track translates exactly -50% for a seamless loop.
  return (
    <div className="mq" aria-hidden="true">
      <div className="mq-track">
        {[0, 1].map(k => (
          <div key={k} style={{ display: "flex" }}>
            {items.map((x, i) => <span key={i} className="mq-item">{x}</span>)}
          </div>
        ))}
      </div>
    </div>
  );
}

// ── Manifesto ─────────────────────────────────────────────────────
function Manifesto({ t, setPage }) {
  const h = t.home;
  return (
    <Reveal as="section" className="sec">
      <div className="wrap">
        <div className="masthead">
          <div className="masthead-top">
            <span className="label">{h.benefitsEb}</span>
            <span className="num">01</span>
          </div>
        </div>

        <div className="manifesto-grid">
          <div>
            <MaskTitle as="h2" className="manifesto-statement" text={h.benefitsTitle} step={45} />
            <p className="lede rise" style={{ "--d": "180ms" }}>{h.benefitsSub}</p>

            <div className="notes">
              {h.benefits.map((b, i) => (
                <div key={i} className="note rise" style={{ "--d": (280 + i * 110) + "ms" }}>
                  <span className="num">{nn(i)}</span>
                  <div>
                    <h4>{b.t}</h4>
                    <p>{b.d}</p>
                  </div>
                </div>
              ))}
            </div>

            <div className="rise" style={{ "--d": "520ms", marginTop: "var(--s7)" }}>
              <button className="tlink" onClick={() => setPage("about")}>{t.nav.learnMore} <Arw /></button>
            </div>
          </div>

          {/* One plate that stretches to the text column's height. Two stacked
              plates ran 460px past the end of the copy and left a hole. */}
          <figure className="manifesto-figure">
            <div className="plate manifesto-plate" style={{ "--d": "140ms" }}>
              <img src="/assets/photos/cuff.jpg" alt="Σφυρήλατο βραχιόλι με υφές και ασημοκόλληση" loading="lazy" />
            </div>
            <figcaption className="plate-cap"><span className="num">01</span><span>{h.benefits[0].t}</span></figcaption>
          </figure>
        </div>
      </div>
    </Reveal>
  );
}

// ── SeminarIndex ──────────────────────────────────────────────────
// The signature interaction: numbered rows sharing one plate that cross-fades
// as you move down them. Deliberately not a grid of cards.
// First frame of each seminar's own photo set; the advanced row has no set.
const SEM_IMAGES = [
  "/assets/seminars/metal/001.jpg",
  "/assets/seminars/wax/001.jpg",
  "/assets/photos/ring-stone.jpg"
];

function SeminarIndex({ t, setPage }) {
  const h = t.home;
  const [active, setActive] = useState(0);

  return (
    <Reveal as="section" className="sec sec-raised">
      <div className="wrap">
        <div className="masthead">
          <div className="masthead-top">
            <span className="label">{h.classesEb}</span>
            <span className="num">02</span>
          </div>
          <div className="masthead-split">
            <MaskTitle as="h2" text={h.classesTitle} step={45} />
            <div className="rise" style={{ "--d": "220ms", justifySelf: "start" }}>
              <button className="tlink" onClick={() => setPage("seminars")}>{t.seminars.eyebrow} <Arw /></button>
            </div>
          </div>
        </div>

        <div className="sem">
          <div className="sem-rows">
            {h.formats.map((f, i) => (
              <button key={i}
                className={"sem-row rise" + (active === i ? " on" : "")}
                style={{ "--d": (i * 120) + "ms" }}
                onMouseEnter={() => setActive(i)}
                onFocus={() => setActive(i)}
                onClick={() => setPage("seminars")}>
                <span className="num">{nn(i)}</span>
                <span className="sem-row-body">
                  <span className="sem-row-title">{f.t}</span>
                  <span className="sem-row-meta">{f.eb}</span>
                  <span className="sem-row-desc">{f.d}</span>
                  {/* Shown only below 620px, where the shared plate is hidden. */}
                  <span className="plate sem-row-plate is-in">
                    <img src={SEM_IMAGES[i]} alt={f.t} loading="lazy" />
                  </span>
                </span>
                <Arw />
              </button>
            ))}
          </div>

          <div className="sem-preview" aria-hidden="true">
            {SEM_IMAGES.map((src, i) => (
              <img key={src} src={src} alt="" className={active === i ? "on" : ""} loading="lazy" />
            ))}
          </div>
        </div>
      </div>
    </Reveal>
  );
}

// ── Slideshow ─────────────────────────────────────────────────────
// Sits under each seminar. A set can run to 68 photos, so only the current
// slide and its two neighbours are mounted — otherwise every image in the set
// would be in the DOM and in flight at once.
function Slideshow({ images, label }) {
  const n = images.length;
  const [i, setI] = useState(0);
  const [paused, setPaused] = useState(false);
  const touch = useRef(null);

  const go = (d) => setI(v => (v + d + n) % n);

  useEffect(() => {
    if (paused || n < 2) return;
    if (window.matchMedia && window.matchMedia("(prefers-reduced-motion: reduce)").matches) return;
    const id = setInterval(() => setI(v => (v + 1) % n), 5200);
    return () => clearInterval(id);
  }, [paused, n]);

  if (!n) return null;

  // Shortest circular distance, so slide 0 and slide n-1 are neighbours.
  const half = Math.floor(n / 2);
  const near = (k) => Math.abs(((k - i + n + half) % n) - half) <= 1;

  const onTouchStart = (e) => { touch.current = e.touches[0].clientX; };
  const onTouchEnd = (e) => {
    if (touch.current == null) return;
    const dx = e.changedTouches[0].clientX - touch.current;
    touch.current = null;
    if (Math.abs(dx) > 45) go(dx < 0 ? 1 : -1);
  };

  return (
    <div className="slideshow"
         onMouseEnter={() => setPaused(true)} onMouseLeave={() => setPaused(false)}
         onFocusCapture={() => setPaused(true)} onBlurCapture={() => setPaused(false)}>
      <div className="slideshow-stage" onTouchStart={onTouchStart} onTouchEnd={onTouchEnd}>
        {images.map((src, k) => near(k) ? (
          <img key={src} src={src} alt={label + " — " + (k + 1)}
               className={k === i ? "on" : ""} draggable="false"
               loading={k === i ? "eager" : "lazy"} />
        ) : null)}
      </div>

      <div className="slideshow-bar">
        <span className="num">{nn(i)} <span className="sep">/</span> {String(n).padStart(2, "0")}</span>
        <div className="slideshow-track" aria-hidden="true">
          <i style={{ transform: "scaleX(" + ((i + 1) / n) + ")" }} />
        </div>
        <div className="slideshow-nav">
          <button onClick={() => go(-1)} aria-label="Previous">‹</button>
          <button onClick={() => go(1)} aria-label="Next">›</button>
        </div>
      </div>
    </div>
  );
}

// ── SpecStrip ─────────────────────────────────────────────────────
function SpecStrip({ t }) {
  const h = t.home;
  const icons = ["users", "check", "home", "book"];
  return (
    <Reveal as="section" className="sec sec-dark">
      <div className="wrap">
        <div className="masthead">
          <div className="masthead-top">
            <span className="label">{h.featuresEb}</span>
            <span className="num">03</span>
          </div>
          <MaskTitle as="h2" text={h.featuresTitle} step={45} />
        </div>
        {/* A ledger: term on the left, definition on the right, ruled horizontally.
            The four vertical cells this replaced were mostly empty. */}
        <div className="ledger">
          {h.features.map((f, i) => (
            <div key={i} className="ledger-row rise" style={{ "--d": (i * 90) + "ms" }}>
              <div className="ledger-k">
                <span className="num">{nn(i)}</span>
                <Icon name={icons[i]} size={18} />
                <h4>{f.t}</h4>
              </div>
              <p className="ledger-v">{f.d}</p>
            </div>
          ))}
        </div>
      </div>
    </Reveal>
  );
}

// ── Maker ─────────────────────────────────────────────────────────
function Maker({ t }) {
  const m = t.team.members[0];
  return (
    <Reveal as="section" className="sec">
      <div className="wrap">
        <div className="masthead">
          <div className="masthead-top">
            <span className="label">{t.team.eb}</span>
            <span className="num">04</span>
          </div>
        </div>

        <div className="maker">
          <figure style={{ margin: 0 }}>
            <div className="plate maker-plate">
              <img src="/assets/photos/katerina.jpg" alt={m.name} loading="lazy" />
            </div>
            <figcaption className="plate-cap"><span className="num">—</span><span>{m.name}</span></figcaption>
          </figure>

          <div>
            <span className="label rise">{m.role}</span>
            <MaskTitle as="h2" className="maker-name" text={m.name} delay={80} step={60} />
            <p className="maker-bio rise" style={{ "--d": "260ms" }}>{m.bio}</p>
            <div className="maker-sig rise" style={{ "--d": "380ms" }}>
              <div className="maker-fact">
                <span className="label">{t.stats.years.l}</span>
                <span className="v">{t.stats.years.n}</span>
              </div>
              <div className="maker-fact">
                <span className="label">{t.stats.group.l}</span>
                <span className="v">{t.stats.group.n}</span>
              </div>
            </div>
          </div>
        </div>
      </div>
    </Reveal>
  );
}

// ── Quotes ────────────────────────────────────────────────────────
// No aggregate score: two reviews carried over from the old site are not a
// rating, and inventing one would be fabricating a number.
function Quotes({ t }) {
  return (
    <Reveal as="section" className="sec sec-dark">
      <div className="wrap">
        <div className="masthead">
          <div className="masthead-top">
            <span className="label">{t.testimonialsLabel}</span>
            <span className="num">05</span>
          </div>
          <MaskTitle as="h2" text={t.home.testiTitle} step={45} />
        </div>
        <div className="quotes">
          {t.testimonials.map((q, i) => {
            const [name, role] = (q.att || "").split(" · ");
            return (
              <blockquote key={i} className="quote rise" style={{ "--d": (i * 140) + "ms", margin: 0 }}>
                <span className="quote-mark" aria-hidden="true">&ldquo;</span>
                <p className="quote-q">{q.q}</p>
                <footer className="quote-att">
                  <span className="n">{name}</span>
                  <span className="r">{role}</span>
                </footer>
              </blockquote>
            );
          })}
        </div>
      </div>
    </Reveal>
  );
}

// ── Closing call ──────────────────────────────────────────────────
function ClosingCall({ t, setPage }) {
  const [booking, setBooking] = useState(false);
  return (
    <Reveal as="section" className="cta">
      <div className="wrap">
        <span className="label">{t.home.ctaBannerEb}</span>
        <MaskTitle as="h2" text={t.home.ctaBannerTitle} step={55} />
        <div className="cta-actions rise" style={{ "--d": "420ms" }}>
          <Btn className="btn-light" onClick={() => setBooking(true)}>{t.nav.book}</Btn>
          <Btn className="btn-light" onClick={() => setPage("contact")}>{t.home.ctaSecondary}</Btn>
        </div>
      </div>
      {booking && <ContactModal t={t} onClose={() => setBooking(false)} />}
    </Reveal>
  );
}

// ── Accordion ─────────────────────────────────────────────────────
function Accordion({ items }) {
  const [open, setOpen] = useState(-1);
  return (
    <div className="acc">
      {items.map((it, i) => (
        <div key={i} className={"acc-item" + (open === i ? " open" : "")}>
          <button className="acc-btn" onClick={() => setOpen(open === i ? -1 : i)} aria-expanded={open === i}>
            <span>{it.q}</span>
            <span className="acc-caret" aria-hidden="true" />
          </button>
          <div className="acc-body"><div><p>{it.a}</p></div></div>
        </div>
      ))}
    </div>
  );
}

// ── SeminarCard — a data sheet, not a pricing card ────────────────
function SeminarCard({ t, tier, index }) {
  const [booking, setBooking] = useState(false);
  return (
    <div className="card rise" style={{ "--d": (index * 120) + "ms" }}>
      <div className="card-head">
        <div>
          <div className="card-title">{tier.t}</div>
          <div className="card-eb">{tier.eb}</div>
        </div>
        <div>
          {/* Seminars the studio has never published a price for say so, rather
              than carrying a number we invented. */}
          <div className={"card-price" + (tier.onRequest ? " on-request" : "")}>{tier.p}</div>
          {!tier.onRequest && <div className="card-note">{t.seminars.priceNote}</div>}
        </div>
      </div>
      <p className="card-desc">{tier.d}</p>
      {tier.spec && tier.spec.length > 0 && (
        <div className="card-spec">
          {tier.spec.map((s, i) => (
            <div key={i}><span className="k">{s.k}</span><span className="v">{s.v}</span></div>
          ))}
        </div>
      )}
      <div className="card-cta">
        <Btn className="btn-solid btn-sm" onClick={() => setBooking(true)}>{t.nav.book}</Btn>
      </div>
      {booking && <ContactModal t={t} onClose={() => setBooking(false)} />}
    </div>
  );
}

// ── Field ─────────────────────────────────────────────────────────
function Field({ label, placeholder, type = "text", multiline, name, required }) {
  return (
    <div className="field">
      <label htmlFor={"f-" + name}>{label}</label>
      {multiline
        ? <textarea id={"f-" + name} rows="4" name={name} required={required} placeholder={placeholder} />
        : <input id={"f-" + name} type={type} name={name} required={required} placeholder={placeholder} />}
    </div>
  );
}

// ── postForm: shared submit for the newsletter + contact form ─────
// Falls back to a prefilled mailto: until FORM_ENDPOINT is filled in.
async function postForm(data, email, subject) {
  if (!FORM_ENDPOINT) {
    const body = Object.entries(data).map(([k, v]) => k + ": " + v).join("\n");
    window.location.href = "mailto:" + email
      + "?subject=" + encodeURIComponent(subject)
      + "&body="    + encodeURIComponent(body);
    return;
  }
  const res = await fetch(FORM_ENDPOINT, {
    method: "POST",
    headers: { "Content-Type": "application/json", "Accept": "application/json" },
    body: JSON.stringify(Object.assign({ _subject: subject }, data))
  });
  if (!res.ok) throw new Error("submit failed: " + res.status);
}

// ── NewsletterForm ────────────────────────────────────────────────
function NewsletterForm({ t }) {
  const [email, setEmail] = useState("");
  const [state, setState] = useState("");   // "" | sending | ok | err

  const submit = async (e) => {
    e.preventDefault();
    if (!email || state === "sending") return;
    setState("sending");
    try { await postForm({ email }, t.contact.email, "Newsletter signup"); setState("ok"); setEmail(""); }
    catch (err) { setState("err"); }
  };

  if (state === "ok") return <p className="foot-msg">{t.newsletter.ok}</p>;

  return (
    <Fragment>
      <form className="foot-sub" onSubmit={submit}>
        <input type="email" required placeholder={t.newsletter.ph} aria-label={t.newsletter.ph}
               value={email} onChange={(e) => setEmail(e.target.value)} />
        <button type="submit" disabled={state === "sending"}>{t.newsletter.cta}</button>
      </form>
      {state === "err" && <p className="foot-msg err">{t.newsletter.err}</p>}
    </Fragment>
  );
}

// ── Footer ────────────────────────────────────────────────────────
function Footer({ t, setPage }) {
  const go = (id) => setPage(id);
  return (
    <footer className="foot">
      <div className="wrap">
        <div className="foot-grid">
          <div>
            <div className="foot-brand" onClick={() => go("home")}>Vortex Arts</div>
            <p className="foot-tag">{t.footer.tagline}</p>
          </div>

          <div>
            <h5>{t.footer.studio}</h5>
            <ul>
              <li><a onClick={() => go("seminars")}>{t.nav.seminars}</a></li>
              <li><a onClick={() => go("gallery")}>{t.nav.gallery}</a></li>
              <li><a onClick={() => go("about")}>{t.nav.about}</a></li>
              <li><a onClick={() => go("contact")}>{t.nav.contact}</a></li>
            </ul>
          </div>

          <div>
            <h5>{t.footer.findEb}</h5>
            <ul>
              <li><a href={mapsHref(t.contact)} target="_blank" rel="noopener noreferrer">{t.contact.addr.join(", ")}</a></li>
              <li><a href={"mailto:" + t.contact.email}>{t.contact.email}</a></li>
              {t.contact.phones.map(p => <li key={p.n}><a href={telHref(p.n)}>{p.n}</a></li>)}
              <li><a href={igHref(t.contact)} target="_blank" rel="noopener noreferrer"><Icon name="instagram" size={13} />@{t.contact.instagram}</a></li>
              <li><a href={t.contact.facebook} target="_blank" rel="noopener noreferrer"><Icon name="facebook" size={13} />Facebook</a></li>
            </ul>
          </div>

          <div>
            <h5>{t.footer.newsletterEb}</h5>
            <ul>{t.contact.hours.map((h, i) => <li key={i}>{h}</li>)}</ul>
            <NewsletterForm t={t} />
          </div>
        </div>

        <div className="foot-bottom">
          <span>© {new Date().getFullYear()} Vortex Arts</span>
          <span>{t.footer.rights}</span>
        </div>
      </div>
    </footer>
  );
}

Object.assign(window, {
  useInView, Reveal, MaskTitle, Btn, Icon, Arw, LangSwitch, Field, nn, mediaList,
  Slideshow,
  TopNav, Hero, IndexRow, Marquee, Manifesto, SeminarIndex, SpecStrip,
  Maker, Quotes, ClosingCall, Accordion, SeminarCard, Footer,
  ContactDetails, ContactModal, NewsletterForm, postForm,
  telHref, mapsHref, igHref
});
