// app.jsx — Exo Studio single-pager
/* global React, ReactDOM, DOMPurify, useTweaks, TweaksPanel, TweakSection, TweakRadio, TweakColor, TweakSlider, TweakToggle */

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

/* ─── Rich-text rendering (admin uses Quill → stores HTML) ───────── */
const RICH_ALLOWED_TAGS = [
  "p", "br", "span", "div", "strong", "em", "u", "s", "b", "i",
  "h2", "h3", "h4", "h5", "h6",
  "ul", "ol", "li",
  "a", "blockquote",
];
const RICH_ALLOWED_ATTR = ["href", "target", "rel", "style", "class"];

function looksLikeHtml(s) {
  return typeof s === "string" && /<\w+[\s\S]*>/.test(s);
}

// Quill 2 хранит списки как <ol><li data-list="bullet"|"ordered">…</li></ol>
// + <span class="ql-ui"> внутри li. Без квилловского CSS это рендерится как
// нумерованный список. Конвертим в стандартные <ul>/<ol>.
function normalizeQuillHtml(html) {
  if (!html || !html.includes("data-list")) return html;
  const tmp = document.createElement("div");
  tmp.innerHTML = html;
  tmp.querySelectorAll("ol, ul").forEach((list) => {
    const items = Array.from(list.children).filter((n) => n.tagName === "LI");
    if (!items.length || !items.some((li) => li.hasAttribute("data-list"))) return;
    const parent = list.parentNode;
    let group = null;
    let groupTag = null;
    for (const li of items) {
      const t = li.getAttribute("data-list") === "bullet" ? "ul" : "ol";
      if (t !== groupTag) {
        group = document.createElement(t);
        parent.insertBefore(group, list);
        groupTag = t;
      }
      li.removeAttribute("data-list");
      li.querySelectorAll("span.ql-ui").forEach((s) => s.remove());
      group.appendChild(li);
    }
    list.remove();
  });
  return tmp.innerHTML;
}

function sanitizeRichHtml(html) {
  if (!html) return "";
  const normalized = normalizeQuillHtml(html);
  if (typeof DOMPurify !== "undefined") {
    return DOMPurify.sanitize(normalized, {
      ALLOWED_TAGS: RICH_ALLOWED_TAGS,
      ALLOWED_ATTR: RICH_ALLOWED_ATTR,
      ADD_ATTR: ["target", "rel"],
    });
  }
  return normalized;
}

function RichText({ value, className, as }) {
  const Tag = as || "div";
  if (!value) return <Tag className={className} />;
  if (looksLikeHtml(value)) {
    const html = sanitizeRichHtml(value);
    return <Tag className={className} dangerouslySetInnerHTML={{ __html: html }} />;
  }
  return <Tag className={className} style={{ whiteSpace: "pre-wrap" }}>{value}</Tag>;
}

const STUDIO_ADDRESS = "Новороссийск, проспект Ленина, 91А";
const MAPS_URL = `https://yandex.ru/maps/?text=${encodeURIComponent(STUDIO_ADDRESS)}`;

// Русские склонения: plural(4, ["направление","направления","направлений"]) → "направления"
function plural(n, forms) {
  const n10 = Math.abs(n) % 10;
  const n100 = Math.abs(n) % 100;
  if (n100 >= 11 && n100 <= 14) return forms[2];
  if (n10 === 1)                return forms[0];
  if (n10 >= 2 && n10 <= 4)     return forms[1];
  return forms[2];
}

const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  "palette": ["#d14a8c", "#0c0810", "#ead6df"],
  "displayFont": "антиква",
  "density": "средне",
  "showMarquee": true
}/*EDITMODE-END*/;

const PALETTES = [
  ["#d14a8c", "#0c0810", "#ead6df"], // noir rose — чёрный и холодная фуксия
  ["#1f6b4e", "#0f1411", "#ece7da"], // emerald
  ["#b8362c", "#15110f", "#f1ebe1"], // bordeaux
  ["#1a1a1a", "#0a0908", "#e6e2d8"], // mono
];

const FONT_OPTS = {
  "антиква":  '"Spectral", "Cormorant Garamond", Georgia, serif',
  "гарамон":   '"Cormorant Garamond", "Spectral", Georgia, serif',
  "классик":  '"PT Serif", "Spectral", Georgia, serif',
};

/* ────────────────────────────────────────────────────────────────
   DATA CONTEXT
   ──────────────────────────────────────────────────────────────── */

const DataCtx = createContext(null);
const useData = () => useContext(DataCtx);

/* ── ценовые хелперы ── */
const fmtRub = (n) => Number(n).toLocaleString("ru-RU");
const sesWord = (n) => {
  const m10 = n % 10, m100 = n % 100;
  if (m10 === 1 && m100 !== 11) return "занятие";
  if (m10 >= 2 && m10 <= 4 && (m100 < 12 || m100 > 14)) return "занятия";
  return "занятий";
};
const durText = (d) => (d === 90 ? "1,5 часа" : "1 час");

// минимальная цена пробного из PB; fallback — если коллекция ещё не загрузилась
function useTrialMin(fallback = 600) {
  const { PRICES } = useData();
  const t = PRICES.filter((p) => p.kind === "trial").map((p) => Number(p.price)).filter(Boolean);
  return t.length ? Math.min(...t) : fallback;
}

/* ────────────────────────────────────────────────────────────────
   NAV
   ──────────────────────────────────────────────────────────────── */

function Nav() {
  return (
    <nav className="nav">
      <a className="nav__logo" href="#top">
        <b>Exo</b> Studio
      </a>
      <div className="nav__links">
        <a href="#dirs">Направления</a>
        <a href="#trainers">Тренеры</a>
        <a href="#gallery">Галерея</a>
        <a href="#schedule">Расписание</a>
        <a href="#prices">Цены</a>
        <a href="#reviews">Отзывы</a>
        <a href="#faq">Новичкам</a>
        <a href="#contacts">Контакты</a>
      </div>
      <div className="nav__right">
        <a className="nav__phone" href="tel:+79956317881">+7 (995) 631-78-81</a>
        <a className="btn btn--accent" href="#book">Записаться</a>
      </div>
    </nav>
  );
}

/* ────────────────────────────────────────────────────────────────
   HERO
   ──────────────────────────────────────────────────────────────── */

function Hero({ showMarquee }) {
  const trialMin = useTrialMin();
  return (
    <section className="hero" id="top">
      <div>
        <h1 className="display hero__title">
          <span className="l1">Танцуйте,</span>
          <span className="l2">как хочется</span>
          <span className="l1">— а не как</span>
          <span className="l2">принято.</span>
        </h1>
        <p className="hero__sub">
          Камерная студия растяжки и танца на пилоне — в сердце Южного района.
          Мини-группы до восьми человек, к каждой — свой подход. Без ограничений
          по возрасту, весу и спортивному прошлому.
        </p>
        <div className="hero__cta">
          <a className="btn btn--accent btn--lg" href="#book">
            Пробное от {fmtRub(trialMin)} ₽ <span>→</span>
          </a>
          <a className="btn btn--ghost btn--lg" href="#dirs">
            Все направления
          </a>
        </div>
        <a className="hero__map" href={MAPS_URL} target="_blank" rel="noopener noreferrer">
          <span className="hero__map-pin">📍</span>
          <span>просп. Ленина, 91А · открыть в Яндекс.Картах</span>
          <span className="hero__map-arrow">↗</span>
        </a>
        <div className="hero__meta">
          <div><span className="k">Зал</span><span className="v">один, 56 м²</span></div>
          <div><span className="k">Пилоны</span><span className="v">четыре, спин/статик</span></div>
          <div><span className="k">Группы</span><span className="v">до 8 человек</span></div>
          <div><span className="k">Часы</span><span className="v">пн–сб · до 22:00</span></div>
        </div>
      </div>

      <HeroVideoPlayer />

      {showMarquee && (
        <div className="hero__marquee">
          <div className="track">
            <span>Pole Exotic</span><span>Strip-пластика</span><span>High Heels</span>
            <span>Pole Sport</span><span>Twerk</span><span>Stretching</span>
            <span>Pole Exotic</span><span>Strip-пластика</span><span>High Heels</span>
            <span>Pole Sport</span><span>Twerk</span><span>Stretching</span>
          </div>
        </div>
      )}
    </section>
  );
}

function HeroVideoPlayer() {
  const { HERO_VIDEO } = useData();
  const src = HERO_VIDEO?.url || "/hero.mp4";
  return (
    <div className="hero__visual hero__visual--video">
      <video
        className="hero__video"
        src={src}
        autoPlay
        loop
        muted
        playsInline
        preload="metadata"
      />
    </div>
  );
}

/* ────────────────────────────────────────────────────────────────
   INFOSTRIP
   ──────────────────────────────────────────────────────────────── */

function InfoStrip() {
  const { TRAINERS } = useData();
  const trialMin = useTrialMin();
  const items = [
    { n: "с 2017", l: "Работаем здесь" },
    { n: "4", l: "Пилона в зале" },
    { n: String(TRAINERS.length), l: "Тренеров" },
    { n: `от ${fmtRub(trialMin)}₽`, l: "Пробное занятие" },
  ];
  return (
    <div className="infostrip">
      {items.map((it) => (
        <div key={it.l}>
          <div className="num-big">{it.n}</div>
          <div className="lbl">{it.l}</div>
        </div>
      ))}
    </div>
  );
}

/* ────────────────────────────────────────────────────────────────
   DIRECTIONS
   ──────────────────────────────────────────────────────────────── */

function Directions({ onPick }) {
  const { DIRECTIONS } = useData();
  return (
    <section className="sect" id="dirs">
      <div className="sect__head">
        <h2 className="sect__h1 display">
          {DIRECTIONS.length === 6 ? "Шесть" : DIRECTIONS.length}<br />
          <em>{plural(DIRECTIONS.length, ["направление", "направления", "направлений"])}.</em>
        </h2>
        <div>
          <div className="sect__count">{DIRECTIONS.length} {plural(DIRECTIONS.length, ["направление", "направления", "направлений"])}</div>
          <p className="sect__intro" style={{ marginTop: 14 }}>
            Большинство приходят за чем-то одним — на exotic или на strip-пластику —
            и так и танцуют годами. Кто-то постепенно пробует соседнее.
            Жёстких связок «начни с этого, потом то» у нас нет.
          </p>
        </div>
      </div>

      <div className="dirs">
        {DIRECTIONS.map((d) => (
          <div
            className="dir"
            key={d.id}
            role="button"
            tabIndex={0}
            aria-label={`Записаться на пробное: ${d.name}${d.accent || ""}`}
            onClick={() => onPick?.(d.id)}
            onKeyDown={(e) => {
              if (e.key === "Enter" || e.key === " ") {
                e.preventDefault();
                onPick?.(d.id);
              }
            }}
          >
            <div className="dir__no">{d.no} · {d.sessions}</div>
            <h3 className="dir__name">
              {d.name}<em>{d.accent}</em>
            </h3>
            <RichText className="dir__desc" value={d.desc} />
            <div className="dir__meta">
              <span className="level">{d.levels}</span>
              <span className="arrow">↗</span>
            </div>
          </div>
        ))}
      </div>
    </section>
  );
}

/* ────────────────────────────────────────────────────────────────
   TRAINERS + MODAL
   ──────────────────────────────────────────────────────────────── */

function TrainerPhoto({ trainer, large }) {
  const url = large ? (trainer.photo_url_lg || trainer.photo_url) : trainer.photo_url;
  if (url) {
    return (
      <img
        src={url}
        alt={`${trainer.first} ${trainer.last}`}
        loading="lazy"
      />
    );
  }
  return <div className="ph" data-label={trainer.photo_label}></div>;
}

function Trainers({ onOpen }) {
  const { TRAINERS } = useData();
  return (
    <section className="sect" id="trainers" style={{ borderTop: "1px solid var(--c-line)" }}>
      <div className="sect__head">
        <h2 className="sect__h1 display">
          Кто будет<br />
          <em>вас ловить.</em>
        </h2>
        <div>
          <div className="sect__count">В команде · {TRAINERS.length} {TRAINERS.length === 1 ? "человек" : "человека"}</div>
          <p className="sect__intro" style={{ marginTop: 14 }}>
            У каждого тренера — свои группы, свои постановки и свой характер.
            Нажмите на карточку, чтобы посмотреть профиль, расписание и записаться лично.
          </p>
        </div>
      </div>

      <div className="trainers">
        {TRAINERS.map((t) => (
          <button className="trainer" key={t.id} onClick={() => onOpen(t)}>
            <div className="trainer__photo">
              <div className="trainer__chip">{t.chip}</div>
              <TrainerPhoto trainer={t} />
            </div>
            <div className="trainer__role">{t.role}</div>
            <h3 className="trainer__name">
              {t.first} <em>{t.last}</em>
            </h3>
          </button>
        ))}
      </div>
    </section>
  );
}

function TrainerModal({ trainer, onClose }) {
  useEffect(() => {
    if (!trainer) return;
    const h = (e) => e.key === "Escape" && onClose();
    document.addEventListener("keydown", h);
    document.body.style.overflow = "hidden";
    return () => {
      document.removeEventListener("keydown", h);
      document.body.style.overflow = "";
    };
  }, [trainer, onClose]);

  return (
    <div className={"modal" + (trainer ? " open" : "")} onClick={onClose}>
      <div className="modal__panel" onClick={(e) => e.stopPropagation()}>
        <div className="modal__head">
          <div className="eyebrow">Профиль тренера{trainer ? ` · ${trainer.first} ${trainer.last}` : ""}</div>
          <button className="modal__close" onClick={onClose} aria-label="Закрыть">✕</button>
        </div>
        {trainer && (
          <>
            <div className="modal__hero">
              <div className="modal__photo">
                <TrainerPhoto trainer={trainer} large />
              </div>
              <div>
                <h2 className="modal__name">
                  {trainer.first}<br /><em>{trainer.last}</em>
                </h2>
                <p className="modal__role">{trainer.role}</p>
                <div className="modal__tags">
                  {trainer.tags.map((tag) => <span key={tag}>{tag}</span>)}
                </div>
              </div>
            </div>

            <div className="modal__stats">
              <div>
                <div className="k">Опыт</div>
                <div className="v">{trainer.stats.exp}</div>
              </div>
              <div>
                <div className="k">Учениц</div>
                <div className="v">{trainer.stats.students}</div>
              </div>
              <div>
                <div className="k">Стиль</div>
                <div className="v" style={{ fontSize: 18, fontStyle: "italic" }}>{trainer.stats.style}</div>
              </div>
            </div>

            <RichText className="modal__bio" value={trainer.bio} />

            {trainer.schedule.length > 0 && (
              <>
                <div className="modal__subhead">Расписание · {trainer.first}</div>
                <div className="modal__times">
                  {trainer.schedule.map((s, i) => (
                    <div className="t" key={i}>
                      <span className="d">{s.d}</span>
                      <div>
                        <div style={{ fontFamily: "var(--f-body)", fontSize: 14 }}>{s.k}</div>
                        <div className="num" style={{ fontSize: 11, color: "var(--c-ink-dim)", marginTop: 2 }}>{s.t}</div>
                      </div>
                      <a className="btn btn--ghost" href="#book" onClick={onClose} style={{ padding: "8px 14px", fontSize: 12 }}>
                        Записаться
                      </a>
                    </div>
                  ))}
                </div>
              </>
            )}

            <a className="btn btn--accent btn--lg" href="#book" onClick={onClose}
               style={{ marginTop: 36, width: "100%", justifyContent: "center" }}>
              Записаться к {trainer.first} <span>→</span>
            </a>
          </>
        )}
      </div>
    </div>
  );
}

/* ────────────────────────────────────────────────────────────────
   SCHEDULE
   ──────────────────────────────────────────────────────────────── */

function Schedule() {
  const { SCHEDULE } = useData();
  const { days, times, sample } = SCHEDULE;
  return (
    <section className="sect" id="schedule" style={{ borderTop: "1px solid var(--c-line)" }}>
      <div className="sect__head">
        <h2 className="sect__h1 display">
          Расписание<br /><em>на неделю.</em>
        </h2>
        <div>
          <div className="sect__count">Пн — Сб · до 22:00</div>
          <p className="sect__intro" style={{ marginTop: 14 }}>
            Сетка обновляется каждый понедельник. Полное расписание с бронью —
            в личном кабинете и в нашем телеграм-боте.
          </p>
        </div>
      </div>

      <div className="sched">
        <div></div>
        {days.map((d) => <div className="day" key={d}>{d}</div>)}
        {times.map((time) => (
          <React.Fragment key={time}>
            <div className="time">{time}</div>
            {days.map((d) => {
              const k = `${time}-${d}`;
              const s = sample[k];
              return (
                <div className={"slot " + (s ? s.type : "")} key={k}>
                  {s && (
                    <>
                      <strong>{s.name}</strong>
                      <span>{s.who}</span>
                    </>
                  )}
                </div>
              );
            })}
          </React.Fragment>
        ))}
      </div>
    </section>
  );
}

/* ────────────────────────────────────────────────────────────────
   PRICES
   ──────────────────────────────────────────────────────────────── */

function Prices() {
  const { PRICES } = useData();
  const packs  = PRICES.filter((p) => p.kind === "pack");
  const trials = PRICES.filter((p) => p.kind === "trial").sort((a, b) => a.duration - b.duration);
  const unlims = PRICES.filter((p) => p.kind === "unlimited");

  const durations = [...new Set(packs.map((p) => p.duration).filter(Boolean))].sort((a, b) => a - b);
  const [durSel, setDurSel] = useState(0);
  const dur = durations.includes(durSel) ? durSel : (durations[0] || 0);

  const rows = packs
    .filter((p) => !p.duration || p.duration === dur)
    .sort((a, b) => (a.sessions - b.sessions) || (a.order - b.order));

  const perSession = (p) => (p.sessions ? Math.round(Number(p.price) / p.sessions) : 0);
  const best = rows.reduce((min, p) => {
    const v = perSession(p);
    return v && (!min || v < min) ? v : min;
  }, 0);

  // пункты, общие для всех абонементов, уходят одной строкой под таблицу;
  // в строках остаются только различия (заморозка)
  const common = rows.length
    ? rows[0].items.filter((it) => rows.every((r) => r.items.includes(it)))
    : [];

  const trialMin = useTrialMin();

  return (
    <section className="sect" id="prices" style={{ borderTop: "1px solid var(--c-line)" }}>
      <div className="sect__head">
        <h2 className="sect__h1 display">
          Сколько<br /><em>стоит.</em>
        </h2>
        <div>
          <div className="sect__count">в рублях, НДС включён</div>
          <p className="sect__intro" style={{ marginTop: 14 }}>
            Оплата картой, СБП или переводом. Первый раз — пробное за {fmtRub(trialMin)} ₽,
            чтобы понять, ваше или не ваше. Дальше — абонемент: чем больше занятий,
            тем дешевле каждое.
          </p>
        </div>
      </div>

      <div className="ptable">
        {trials.length > 0 && (
          <a className="ptable__row ptable__row--edge" href="#book">
            <div className="ptable__name">Пробное<em> занятие</em></div>
            <div className="ptable__edgeprice">
              {trials.map((t, i) => (
                <span key={t.id}>
                  {i > 0 && <i> / </i>}
                  {fmtRub(t.price)} ₽ <small>· {durText(t.duration)}</small>
                </span>
              ))}
            </div>
            <div className="ptable__go">Записаться <span>→</span></div>
          </a>
        )}

        {durations.length > 1 && (
          <div className="ptable__switch">
            <span className="ptable__switchlabel">Занятие</span>
            {durations.map((d) => (
              <button
                key={d}
                type="button"
                className={d === dur ? "active" : ""}
                onClick={() => setDurSel(d)}
              >
                {durText(d)}
              </button>
            ))}
          </div>
        )}

        {rows.map((p) => {
          const ps = perSession(p);
          const extras = p.items.filter((it) => !common.includes(it));
          return (
            <a
              key={p.id}
              className={"ptable__row ptable__row--pack" + (p.featured ? " is-featured" : "")}
              href="#book"
            >
              <div className="ptable__name">{p.sessions}<em> {sesWord(p.sessions)}</em></div>
              <div className="ptable__price">{fmtRub(p.price)}<sup>₽</sup></div>
              <div className="ptable__ps">{ps ? `${ps} ₽ / занятие` : p.per}</div>
              <div className="ptable__tags">
                {p.featured && <span className="ptable__tag is-hot">популярный</span>}
                {ps > 0 && ps === best && <span className="ptable__tag">лучшая цена</span>}
                {extras.map((it) => <span key={it} className="ptable__extra">{it}</span>)}
              </div>
              <div className="ptable__arrow">→</div>
            </a>
          );
        })}

        {unlims.map((u) => (
          <a key={u.id} className="ptable__row ptable__row--edge" href="#book">
            <div className="ptable__name">{u.name}<em> · {u.accent}</em></div>
            <div className="ptable__edgeprice">
              <span>{fmtRub(u.price)} ₽ <small>· {u.per}</small></span>
            </div>
            <div className="ptable__go">{u.cta || "Купить"} <span>→</span></div>
          </a>
        ))}
      </div>

      {common.length > 0 && (
        <div className="ptable__note">
          Во всех абонементах: {common.join(" · ").toLowerCase()}. Срок действия — 30 дней.
        </div>
      )}
    </section>
  );
}

/* ────────────────────────────────────────────────────────────────
   FAQ
   ──────────────────────────────────────────────────────────────── */

function FaqSection() {
  const { FAQ } = useData();
  const [open, setOpen] = useState(0);
  return (
    <section className="sect" id="faq" style={{ borderTop: "1px solid var(--c-line)" }}>
      <div className="sect__head">
        <h2 className="sect__h1 display">
          Стесняетесь?<br /><em>Это нормально.</em>
        </h2>
        <div>
          <div className="sect__count">Частые вопросы · для новичков</div>
          <p className="sect__intro" style={{ marginTop: 14 }}>
            {FAQ.length} самых частых вопросов, которые нам задают перед первым занятием.
            Если вашего здесь нет — пишите в Telegram, отвечаем в течение часа.
          </p>
        </div>
      </div>

      <div className="faq">
        {FAQ.map((f, i) => (
          <div
            key={i}
            className={"faq__item" + (open === i ? " open" : "")}
            onClick={() => setOpen(open === i ? -1 : i)}
          >
            <div className="faq__no">№{String(i + 1).padStart(2, "0")}</div>
            <div>
              <h3 className="faq__q">{f.q}</h3>
              <RichText className="faq__a" value={f.a} />
            </div>
            <div className="faq__plus">+</div>
          </div>
        ))}
      </div>
    </section>
  );
}

/* ────────────────────────────────────────────────────────────────
   BOOK FORM — реальный POST в PocketBase
   ──────────────────────────────────────────────────────────────── */

function BookForm({ preset, onConsumed }) {
  const { DIRECTIONS, PRICES } = useData();
  const trials = PRICES.filter((p) => p.kind === "trial").sort((a, b) => a.duration - b.duration);
  const trialMin = useTrialMin();
  const [form, setForm] = useState({
    name: "", phone: "", tg: "",
    direction: "", level: "Пробное",
    when: "", comment: "", consent: true,
  });

  useEffect(() => {
    if (preset) {
      setForm((f) => ({ ...f, direction: preset }));
      onConsumed?.();
    }
  }, [preset, onConsumed]);
  const [errors, setErrors] = useState({});
  const [state, setState] = useState("idle"); // idle | sending | done | error
  const [serverErr, setServerErr] = useState("");
  const [ticket, setTicket] = useState(null);

  const set = (k, v) => setForm((f) => ({ ...f, [k]: v }));

  const validate = () => {
    const e = {};
    if (form.name.trim().length < 2) e.name = "Как вас зовут?";
    const digits = form.phone.replace(/\D/g, "");
    if (digits.length < 10) e.phone = "Похоже на неполный номер";
    if (!form.direction) e.direction = "Выберите направление";
    if (!form.consent) e.consent = "Нужно согласие";
    return e;
  };

  const submit = async (ev) => {
    ev.preventDefault();
    const e = validate();
    setErrors(e);
    if (Object.keys(e).length) return;
    setState("sending");
    setServerErr("");

    const payload = {
      name: form.name.trim(),
      phone: form.phone.trim(),
      tg: form.tg.trim(),
      direction: form.direction,
      level: form.level,
      when: form.when.trim(),
      comment: form.comment.trim(),
      consent: form.consent,
      status: "new",
      source: "site",
      meta: {
        ua: navigator.userAgent,
        ref: document.referrer || null,
      },
    };

    try {
      const r = await fetch("/api/collections/bookings/records", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify(payload),
      });
      if (!r.ok) {
        const j = await r.json().catch(() => ({}));
        throw new Error(j.message || `HTTP ${r.status}`);
      }
      const j = await r.json();
      setTicket(j.id ? j.id.slice(-6).toUpperCase() : "—");
      setState("done");
    } catch (err) {
      console.error("booking submit failed:", err);
      setServerErr(err.message || "Не удалось отправить");
      setState("error");
    }
  };

  if (state === "done") {
    return (
      <section className="sect" id="book" style={{ borderTop: "1px solid var(--c-line)" }}>
        <div className="book">
          <div className="book__left">
            <div className="eyebrow">Заявка №{ticket} · Принята</div>
            <h2 className="book__h1">
              Готово.<br />
              <em>До встречи на пилоне.</em>
            </h2>
            <p className="book__lead">
              Мы получили заявку. Администратор Лена напишет вам в течение часа
              в Telegram или позвонит — выбирайте, как удобнее. Если что-то срочное —
              стучите нам в мессенджеры прямо сейчас.
            </p>
            <div className="book__alt">
              <span>Хотите сразу решить вопрос?</span>
              <a href="https://t.me/exostudio.nvrsk" target="_blank" rel="noreferrer">Telegram администратора →</a>
              <a href="tel:+79956317881">+7 (995) 631-78-81 →</a>
            </div>
          </div>
          <div className="form__success">
            <div className="eyebrow" style={{ color: "rgba(235,227,213,0.5)" }}>Что дальше</div>
            <h3>1. Подтверждаем <em>занятие</em></h3>
            <p>Лена позвонит в течение часа, согласует точное время и тренера.</p>
            <h3>2. Приходите за <em>15 минут</em></h3>
            <p>Покажем раздевалку, выдадим каблуки или подстелим коврик. Душ, фен, шампунь — на месте.</p>
            <h3>3. Танцуйте и <em>выдыхайте</em></h3>
            <p>После урока — чай, разбор полетов. Если всё ок, выберете абонемент со скидкой 10%.</p>
            <div className="row">
              <a href="https://t.me/exostudio.nvrsk" target="_blank" rel="noreferrer">Telegram</a>
              <a href="https://vk.com/exostudio.nvrsk" target="_blank" rel="noreferrer">VK</a>
              <a href="https://instagram.com/exostudio.nvrsk" target="_blank" rel="noreferrer">Instagram</a>
            </div>
          </div>
        </div>
      </section>
    );
  }

  return (
    <section className="sect" id="book" style={{ borderTop: "1px solid var(--c-line)" }}>
      <div className="book">
        <div className="book__left">
          <div className="eyebrow">
            {(() => {
              const sel = DIRECTIONS.find((d) => d.id === form.direction);
              const tail = sel ? ` · ${(sel.name + (sel.accent || "")).trim()}` : "";
              const price = form.level === "Разовое" ? "900 ₽" : `от ${fmtRub(trialMin)} ₽`;
              const kind = form.level || "Пробное";
              return `Запись · ${kind} · ${price}${tail}`;
            })()}
          </div>
          <h2 className="book__h1">
            Запишитесь<br />
            <em>на занятие.</em>
          </h2>
          <p className="book__lead">
            Лена перезвонит в течение часа, подберёт время и тренера.
            Пробное — {trials.length
              ? trials.map((t) => `${fmtRub(t.price)} ₽ (${durText(t.duration)})`).join(" или ")
              : "от 600 ₽"}, разовое — 900 ₽, оплата на месте.
            Платить сейчас ничего не нужно.
          </p>
          <div className="book__alt">
            <span>Не любите формы?</span>
            <a href="https://t.me/exostudio.nvrsk" target="_blank" rel="noreferrer">Написать в Telegram →</a>
            <a href="https://wa.me/79956317881" target="_blank" rel="noreferrer">WhatsApp →</a>
            <a href="tel:+79956317881">+7 (995) 631-78-81</a>
          </div>
        </div>

        <form className="form" onSubmit={submit} noValidate>
          <div className={"field" + (errors.name ? " field--err" : "")}>
            <label>Как вас зовут</label>
            <input value={form.name} onChange={(e) => set("name", e.target.value)} placeholder="Имя" />
            {errors.name && <div className="field__err">{errors.name}</div>}
          </div>

          <div className="field--row">
            <div className={"field" + (errors.phone ? " field--err" : "")}>
              <label>Телефон</label>
              <input value={form.phone} onChange={(e) => set("phone", e.target.value)} placeholder="+7 (___) ___-__-__" inputMode="tel" />
              {errors.phone && <div className="field__err">{errors.phone}</div>}
            </div>
            <div className="field">
              <label>Telegram</label>
              <input value={form.tg} onChange={(e) => set("tg", e.target.value)} placeholder="@nick (необязательно)" />
            </div>
          </div>

          <div className={"field" + (errors.direction ? " field--err" : "")}>
            <label>Направление</label>
            <div className="chips">
              {DIRECTIONS.map((d) => {
                const lbl = (d.name + d.accent).trim();
                return (
                  <button
                    type="button"
                    key={d.id}
                    className={"chip" + (form.direction === d.id ? " active" : "")}
                    onClick={() => set("direction", d.id)}
                  >
                    {lbl}
                  </button>
                );
              })}
            </div>
            {errors.direction && <div className="field__err">{errors.direction}</div>}
          </div>

          <div className="field--row">
            <div className="field">
              <label>Тип</label>
              <div className="chips">
                {["Пробное", "Разовое"].map((l) => (
                  <button
                    type="button"
                    key={l}
                    className={"chip" + (form.level === l ? " active" : "")}
                    onClick={() => set("level", l)}
                  >
                    {l}
                  </button>
                ))}
              </div>
            </div>
            <div className="field">
              <label>Удобный день / время</label>
              <input value={form.when} onChange={(e) => set("when", e.target.value)} placeholder="Например, чт вечер" />
            </div>
          </div>

          <div className="field">
            <label>Комментарий</label>
            <input value={form.comment} onChange={(e) => set("comment", e.target.value)} placeholder="Если хотите что-то добавить" />
          </div>

          <label className={"consent" + (errors.consent ? " field--err" : "")}>
            <input type="checkbox" checked={form.consent} onChange={(e) => set("consent", e.target.checked)} />
            <span>Согласна на обработку персональных данных. Не передаём третьим лицам и не пишем в 7 утра.</span>
          </label>

          {state === "error" && (
            <div className="field__err" style={{ marginTop: 4 }}>
              Не удалось отправить заявку: {serverErr}. Попробуйте ещё раз или напишите в Telegram.
            </div>
          )}

          <button className="submit" type="submit" disabled={state === "sending"}>
            <span>{state === "sending" ? "Отправляем…" : "Записаться"}</span>
            <span className="arrow">→</span>
          </button>
        </form>
      </div>
    </section>
  );
}

/* ────────────────────────────────────────────────────────────────
   GALLERY
   ──────────────────────────────────────────────────────────────── */

function Gallery() {
  const { GALLERY } = useData();
  const [open, setOpen] = useState(null);

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

  if (!GALLERY || GALLERY.length === 0) return null;

  return (
    <section className="sect" id="gallery" style={{ borderTop: "1px solid var(--c-line)" }}>
      <div className="sect__head">
        <div>
          <div className="eyebrow">Атмосфера · {GALLERY.length} {plural(GALLERY.length, ["кадр", "кадра", "кадров"])}</div>
          <h2 className="sect__h1">
            Студия <em>изнутри.</em>
          </h2>
        </div>
        <p className="sect__intro">
          Зал, пилоны, ночной свет, зеркала. Атмосфера, ради которой возвращаются.
        </p>
      </div>

      <div className="gallery">
        {GALLERY.map((g) => g.placeholder ? (
          <div className="gallery__item gallery__item--ph" key={g.id}>
            <div className="ph" data-label={g.caption || "ФОТО"} />
          </div>
        ) : (
          <button
            type="button"
            className="gallery__item"
            key={g.id}
            onClick={() => setOpen({ url: g.url_lg || g.url, caption: g.caption })}
            aria-label={g.caption || "Открыть фото"}
          >
            <img src={g.url} alt={g.caption || ""} loading="lazy" />
            {g.caption && <div className="gallery__cap">{g.caption}</div>}
          </button>
        ))}
      </div>

      {open && (
        <div className="lightbox" onClick={() => setOpen(null)} role="dialog" aria-modal="true">
          <button className="lightbox__close" onClick={(e) => { e.stopPropagation(); setOpen(null); }} aria-label="Закрыть">✕</button>
          <img src={open.url} alt={open.caption || ""} onClick={(e) => e.stopPropagation()} />
          {open.caption && <div className="lightbox__cap">{open.caption}</div>}
        </div>
      )}
    </section>
  );
}

/* ────────────────────────────────────────────────────────────────
   REVIEWS
   ──────────────────────────────────────────────────────────────── */

const REVIEW_SOURCE_LABEL = {
  yandex:    "Yandex Maps",
  vk:        "ВКонтакте",
  telegram:  "Telegram",
  google:    "Google",
  "on-site": "Лично",
  other:     "",
};

function Reviews() {
  const { REVIEWS } = useData();
  if (!REVIEWS || REVIEWS.length === 0) return null;

  return (
    <section className="sect" id="reviews" style={{ borderTop: "1px solid var(--c-line)" }}>
      <div className="sect__head">
        <div>
          <div className="eyebrow">Отзывы</div>
          <h2 className="sect__h1">
            Что говорят <em>наши.</em>
          </h2>
        </div>
        <p className="sect__intro">
          Истории тех, кто пришёл «попробовать» — и остался.
          Все — реальные, без правок и фотошопа.
        </p>
      </div>

      <div className="reviews">
        {REVIEWS.map((r) => {
          const srcLabel = REVIEW_SOURCE_LABEL[r.source] || "";
          return (
            <article className="review" key={r.id}>
              <div className="review__mark" aria-hidden="true">«</div>
              <RichText className="review__text" value={r.text} />
              <div className="review__meta">
                <span className="review__name">{r.name}</span>
                {r.rating ? (
                  <span className="review__stars" aria-label={`Рейтинг: ${r.rating} из 5`}>
                    {"★".repeat(r.rating)}<span className="review__stars-dim">{"★".repeat(5 - r.rating)}</span>
                  </span>
                ) : null}
                {srcLabel && (
                  <span className="review__src">
                    {r.source_url ? (
                      <a href={r.source_url} target="_blank" rel="noreferrer">{srcLabel} ↗</a>
                    ) : srcLabel}
                  </span>
                )}
                {r.date && <span className="review__date">{r.date}</span>}
              </div>
            </article>
          );
        })}
      </div>
    </section>
  );
}

/* ────────────────────────────────────────────────────────────────
   FOOTER
   ──────────────────────────────────────────────────────────────── */

function Footer() {
  return (
    <footer id="contacts">
      <div className="foot">
        <div>
          <div className="eyebrow">Контакты</div>
          <div className="foot__brand" style={{ marginTop: 18 }}>
            <b>Exo</b> Studio<sup style={{ fontSize: "0.3em", verticalAlign: "super", color: "var(--c-ink-faint)", marginLeft: 8 }}>™</sup>
          </div>
          <p style={{ color: "var(--c-ink-dim)", marginTop: 18, maxWidth: "32ch", lineHeight: 1.6 }}>
            <a className="addr-link" href={MAPS_URL} target="_blank" rel="noreferrer">Новороссийск, просп. Ленина, 91А</a>.<br />
            Вход со стороны дороги — по лестнице наверх и направо до упора.<br />
            Ежедневно до 22:00.
          </p>
        </div>
        <div className="foot__col">
          <h4>Связаться</h4>
          <a href="tel:+79956317881">+7 (995) 631-78-81</a>
          <a href="mailto:hi@exostudio.nvrsk.ru">hi@exostudio.nvrsk.ru</a>
          <a href="https://t.me/exostudio.nvrsk" target="_blank" rel="noreferrer">Telegram-бот</a>
          <a href="https://wa.me/79956317881" target="_blank" rel="noreferrer">WhatsApp</a>
        </div>
        <div className="foot__col">
          <h4>Мы в соцсетях</h4>
          <a href="https://t.me/exostudio.nvrsk" target="_blank" rel="noreferrer">Telegram · @exostudio.nvrsk</a>
          <a href="https://vk.com/exostudio.nvrsk" target="_blank" rel="noreferrer">VK · /exostudio.nvrsk</a>
          <a href="https://instagram.com/exostudio.nvrsk" target="_blank" rel="noreferrer">Instagram · @exostudio.nvrsk</a>
          <a href="https://tiktok.com/@exostudio.nvrsk" target="_blank" rel="noreferrer">TikTok · @exostudio.nvrsk</a>
          <a href="https://youtube.com/@exostudio.nvrsk" target="_blank" rel="noreferrer">YouTube · постановки</a>
        </div>
        <div className="foot__legal">
          <span>© 2017 — 2026 · Exo Studio</span>
          <span>ИНН 2315012345 · ОГРНИП 312345678901234</span>
          <span>Дизайн и свёрстка — своя</span>
        </div>
      </div>
    </footer>
  );
}

/* ────────────────────────────────────────────────────────────────
   TWEAKS
   ──────────────────────────────────────────────────────────────── */

function ThemeTweaks({ t, setTweak }) {
  return (
    <TweaksPanel>
      <TweakSection label="Палитра" />
      <TweakColor
        label="Палитра"
        value={t.palette}
        options={PALETTES}
        onChange={(v) => setTweak("palette", v)}
      />

      <TweakSection label="Шрифты" />
      <TweakRadio
        label="Шрифт заголовков"
        value={t.displayFont}
        options={["антиква", "гарамон", "классик"]}
        onChange={(v) => setTweak("displayFont", v)}
      />

      <TweakSection label="Композиция" />
      <TweakRadio
        label="Плотность"
        value={t.density}
        options={["плотно", "средне", "воздушно"]}
        onChange={(v) => setTweak("density", v)}
      />
      <TweakToggle
        label="Бегущая строка"
        value={t.showMarquee}
        onChange={(v) => setTweak("showMarquee", v)}
      />
    </TweaksPanel>
  );
}

/* ────────────────────────────────────────────────────────────────
   APP
   ──────────────────────────────────────────────────────────────── */

function App() {
  const [t, setTweak] = useTweaks(TWEAK_DEFAULTS);
  const [trainer, setTrainer] = useState(null);
  const [presetDir, setPresetDir] = useState(null);

  const pickDirection = useCallback((id) => {
    setPresetDir(id);
    document.getElementById("book")?.scrollIntoView({ behavior: "smooth", block: "start" });
  }, []);
  const consumeDirection = useCallback(() => setPresetDir(null), []);

  useEffect(() => {
    const r = document.documentElement;
    const p = t.palette || PALETTES[0];
    r.style.setProperty("--c-accent", p[0]);
    r.style.setProperty("--c-bg", p[1]);
    r.style.setProperty("--c-paper", p[2]);
    const isNoir = p[0].toLowerCase() === "#d14a8c";
    r.style.setProperty("--c-accent-2", isNoir ? "#7c3aa8" : p[0]);
    r.style.setProperty("--f-display", FONT_OPTS[t.displayFont] || FONT_OPTS["антиква"]);
    document.body.dataset.density = t.density || "средне";
  }, [t.palette, t.displayFont, t.density]);

  return (
    <>
      <Nav />
      <Hero showMarquee={t.showMarquee !== false} />
      <InfoStrip />
      <Directions onPick={pickDirection} />
      <Trainers onOpen={setTrainer} />
      <Gallery />
      <Schedule />
      <Prices />
      <Reviews />
      <FaqSection />
      <BookForm preset={presetDir} onConsumed={consumeDirection} />
      <Footer />

      <TrainerModal trainer={trainer} onClose={() => setTrainer(null)} />
      <ThemeTweaks t={t} setTweak={setTweak} />
    </>
  );
}

/* ────────────────────────────────────────────────────────────────
   ROOT — загрузка данных перед рендером
   ──────────────────────────────────────────────────────────────── */

function Loader({ message }) {
  return (
    <div style={{
      minHeight: "60vh",
      display: "flex",
      alignItems: "center",
      justifyContent: "center",
      fontFamily: "var(--f-body, system-ui)",
      color: "var(--c-ink-dim, #777)",
      letterSpacing: "0.05em",
      fontSize: 13,
    }}>
      {message || "Загружаем…"}
    </div>
  );
}

function Root() {
  const [data, setData] = useState(null);
  const [error, setError] = useState(null);

  useEffect(() => {
    window.STRIP_LOAD()
      .then(setData)
      .catch((e) => {
        console.error("data load failed:", e);
        setError(e.message || String(e));
      });
  }, []);

  if (error) {
    return (
      <Loader message={`Не удалось загрузить контент: ${error}. Попробуйте обновить страницу.`} />
    );
  }
  if (!data) return <Loader />;

  return (
    <DataCtx.Provider value={data}>
      <App />
    </DataCtx.Provider>
  );
}

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