/* Frankenplatz — «Голоса форума» (voices.html). Экспортирует window.Voices.
   Структура и тексты — по согласованной странице коллег (uploads/golosa-foruma.html, 24.09.2026),
   оформление — токены и каркас сайта. Данные: site/voices-data.js (FP_VOICES).
   secret — имя не печатаем: «Секретный спикер» (раскроем на сцене) или «Имя объявим». */
const VNS = window.FrankenplatzDesignSystem_144b92 || window.FrankenplatzDesignSystem_189784 || {};
const { TopBar, Footer, Eyebrow, Button, ReliefBand } = VNS;
const { useState, useEffect } = React;

/* ── Типограф v2 (то же ядро, что в golosa-foruma.html / site/typo.js).
   typo(строка) → строка с U+00A0. Tx — React-обёртка: слова через дефис переносятся
   целиком или только ПЕРЕД дефисом. Весь видимый текст идёт через <Tx> или typo(). ── */
const FPTypo = window.FPTypo;
const NB = "\u00A0";
const typo = (s) => typeof s === "string" && s ? FPTypo.typo(s.replace(/\s*\n\s*/g, " ")) : s;
const RE_HY = /((?:[^\s\u00A0]+\u00A0)*)([\u0410-\u044f\u0401\u0451A-Za-z\u00C0-\u00FF\d]+(?:-[\u0410-\u044f\u0401\u0451A-Za-z\u00C0-\u00FF\d]+)+)([.,:;!?\u00BB)\u2026]*)/g;
function Tx({ children }) {
  const s = typo(children);
  if (typeof s !== "string") return s;
  const out = []; let last = 0, m, k = 0;
  RE_HY.lastIndex = 0;
  while ((m = RE_HY.exec(s))) {
    if (m.index > last) out.push(s.slice(last, m.index));
    const w = m[2], i = w.indexOf("-");
    out.push(<span key={k++} style={{ display: "inline-block", maxWidth: "100%", hyphens: "manual", WebkitHyphens: "manual" }}>{m[1] + w.slice(0, i)}<wbr /><span style={{ whiteSpace: "nowrap", hyphens: "manual", WebkitHyphens: "manual" }}>{w.slice(i) + (m[3] || "")}</span></span>);
    last = m.index + m[0].length;
  }
  if (last < s.length) out.push(s.slice(last));
  return <>{out}</>;
}

const FLAGS = {
  ch: <><rect width="32" height="32" fill="#FF0000"></rect><rect x="13.5" y="7" width="5" height="18" fill="#fff"></rect><rect x="7" y="13.5" width="18" height="5" fill="#fff"></rect></>,
  fr: <><rect width="32" height="32" fill="#fff"></rect><rect width="10.7" height="32" fill="#0055A4"></rect><rect x="21.3" width="10.7" height="32" fill="#EF4135"></rect></>,
  it: <><rect width="32" height="32" fill="#fff"></rect><rect width="10.7" height="32" fill="#009246"></rect><rect x="21.3" width="10.7" height="32" fill="#CE2B37"></rect></>,
  de: <><rect width="32" height="10.7" fill="#000"></rect><rect y="10.7" width="32" height="10.7" fill="#DD0000"></rect><rect y="21.4" width="32" height="10.6" fill="#FFCE00"></rect></>,
  rs: <><rect width="32" height="10.7" fill="#C6363C"></rect><rect y="10.7" width="32" height="10.7" fill="#0C4076"></rect><rect y="21.4" width="32" height="10.6" fill="#fff"></rect></>,
  eu: <><rect width="32" height="32" fill="#003399"></rect><circle cx="16" cy="16" r="7.5" fill="none" stroke="#FFCC00" strokeWidth="2" strokeDasharray="1.6 3.1"></circle></>
};
const Flag = ({ c, title }) => <svg className="vc-flag" viewBox="0 0 32 32" role="img" aria-label={title || ""}>{FLAGS[c] || FLAGS.eu}</svg>;

const VDAYS = {
  0: { full: "Четверг, 12 ноября", date: "12 ноября", tab: "Лозанна · 12 ноября", short: "12.11", sub: "Отдельный вечер во французской Швейцарии" },
  1: { full: "Суббота, 14 ноября", date: "14 ноября", tab: "Баден · 14 ноября", short: "14.11", sub: "Баден. Личные деньги: работа, здоровье, налоги, жильё, семья" },
  2: { full: "Воскресенье, 15 ноября", date: "15 ноября", tab: "Баден · 15 ноября", short: "15.11", sub: "Баден. Бизнес и капитал: инвестиции, недвижимость, риски, наследие" }
};
const TAB_M = { all: "Все дни", 1: "Баден · 14 ноября", 2: "Баден · 15 ноября", 0: "Лозанна · 12 ноября" };
const TABS = [["all", "Все вместе"], ["1", VDAYS[1].tab], ["2", VDAYS[2].tab], ["0", VDAYS[0].tab]];

function plural(n, a, b, c) {
  const m = n % 100, k = n % 10;
  return n + NB + (m >= 11 && m <= 14 ? c : k === 1 ? a : k >= 2 && k <= 4 ? b : c);
}
const inDay = (p, d) => d === "all" || (p.days || []).indexOf(+d) !== -1;

/* Одна тема в двух днях (Лозанна везёт темы Бадена) — печатаем один раз, вторую дату приписываем. */
function mergeTalks(talks) {
  const seen = {}, out = [];
  (talks || []).forEach((x) => {
    if (seen[x.t] !== undefined) { out[seen[x.t]].also.push(x.d); return; }
    seen[x.t] = out.length; out.push({ d: x.d, dn: x.dn, time: x.time, t: x.t, also: [] });
  });
  return out;
}

function Head({ p }) {
  return p.secret ? <span className="vc-seal"><Tx>{p.secret}</Tx></span> : <span className="vc-cat">{p.type}</span>;
}
/* Тема на карточке: каждое предложение с новой строки. «Масштаб без паники.
   Бизнес за пределами одной страны» иначе ломалось посреди второй мысли.
   Короткий первый кусок («Масштаб.») приклеиваем к следующему — строка
   из одного слова выглядит обрывком. */
function topicLines(t) {
  if (String(t || "").indexOf("\n") !== -1) return String(t).split("\n").map((x) => x.trim()).filter(Boolean);
  const parts = (String(t || "").match(/[^.?!]+[.?!]*(?:\s+|$)/g) || [String(t || "")])
    .map((s) => s.trim()).filter(Boolean);
  const out = [];
  parts.forEach((p) => {
    if (out.length && out[out.length - 1].length < 14) out[out.length - 1] += " " + p;
    else out.push(p);
  });
  return out;
}

function Who({ p }) {
  if (p.secret) return (
    <p className="vc-who"><b><Flag c={p.flag} title={p.country} /><span className="vc-mask" aria-hidden="true">• • • • •</span></b>
      <Tx>{p.mask || (p.secret === "Имя объявим" ? COPY.tba : p.secret === "Спикер уточняется" ? COPY.later : COPY.onStage)}</Tx></p>);
  return <p className="vc-who"><b><Flag c={p.flag} title={p.country} /><span><Tx>{p.name}</Tx></span></b><Tx>{p.role}</Tx></p>;
}

function Tile({ p, day, onOpen }) {
  const t = mergeTalks((p.talks || []).filter((x) => day === "all" || x.d === +day));
  const r = t[0] || { d: null, also: [] };
  return (
    <li>
      <div role="button" tabIndex={0} className={"vc-tile" + (p.secret ? " is-secret" : "")} onClick={onOpen} onKeyDown={(e) => { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); onOpen(); } }}>
        <Head p={p} />
        <span className="vc-when">
          <span className="vc-when__d">{r.dn ? r.dn : r.d === null || r.d === undefined ? "День уточняется" : VDAYS[r.d].full}
            {r.also.length ? <span className="vc-when__also"> + {r.also.map((d) => VDAYS[d].short).join(" · ")}</span> : null}</span>
          {/* Времени нет ни у кого — объявим 15.10 (решение 25.09). */}
          <span className="vc-when__t">Время объявим 15.10</span>
        </span>
        <h3 className="vc-topic">{topicLines(r.t || "Тема уточняется").map((l, i) =>
          <span className="vc-topic__l" key={i}><Tx>{l}</Tx></span>)}</h3>
        <Who p={p} />
        <span className="vc-mono" aria-hidden="true">{p.secret ? "?" : p.ini}</span>
        <span className="vc-more">{p.secret ? "Что известно" : "О спикере"}</span>
      </div>
    </li>);
}

function Sheet({ p, onClose }) {
  useEffect(() => {
    const k = (e) => { if (e.key === "Escape") onClose(); };
    document.addEventListener("keydown", k);
    const o = document.body.style.overflow; document.body.style.overflow = "hidden";
    return () => { document.removeEventListener("keydown", k); document.body.style.overflow = o; };
  }, []);
  const t = mergeTalks(p.talks);
  const date = (d) => d === null || d === undefined ? "Дата уточняется" : VDAYS[d].date;
  return (
    <div className="vc-sheet" role="dialog" aria-modal="true" aria-label={"Досье: " + (p.secret ? p.secret : p.name)}>
      <div className="vc-sheet__scrim" onClick={onClose}></div>
      <div className={"vc-sheet__card" + (p.secret ? " is-secret" : "")}>
        <button type="button" className="vc-sheet__x" aria-label="Закрыть" onClick={onClose}>✕</button>
        <Head p={p} />
        <Who p={p} />
        <p className="vc-bio"><Tx>{p.about}</Tx></p>
        <ul className="vc-facts">{(p.dossier || []).map((f, i) => <li key={i}><span><Tx>{f}</Tx></span></li>)}</ul>
        {t.length ?
        <div className="vc-sprog">
          <p className="vc-sprog__lbl">{t.length > 1 ? "Выступления" : "Выступление"}</p>
          <ul>{t.map((r, i) =>
            <li key={i}>
              <span className="vc-sprog__d">{r.dn ? r.dn : date(r.d)}</span>
              <span className="vc-sprog__t"><Tx>{r.t}</Tx></span>
              {r.also.length ? <span className="vc-sprog__x">Также {r.also.map(date).join(" · ")}</span> : null}
            </li>)}
          </ul>
        </div> : null}
      </div>
    </div>);
}

/* Статичный текст — константами: так он тоже проходит через Tx. */
const COPY = {
  lede: "15 спикеров говорят о своих деньгах. Показывают цифры и рассказывают о своём опыте.",
  lau: "Вечер во французской Швейцарии, под Лозанной",
  hintB: "Пятеро спикеров пока без имени.",
  hint: "Трое назовут себя на сцене, иначе вы заранее узнаете, чем закончилась их история. Имена ещё двоих объявим ближе к форуму. И ещё пять тем утверждены, а спикера для них мы пока ищем.",
  tba: "Объявим ближе к форуму",
  onStage: "Имя раскроем на сцене",
  later: "Имя объявим позже",
  empty: "В этот день пока никого нет",
  lauNote: "Ведём переговоры с площадкой в Лозанне — а ты уже можешь записаться в лист ожидания.",
  lauBtn: "Записаться в лист ожидания",
  lauReg: "Вечер под Лозанной, 12 ноября — лист ожидания",
  lauRegSub: "Площадку ещё выбираем. Оставь контакты — напишем, как только место и время будут известны.",
  endLau: "Французская Швейцария, 12 ноября",
  endLauBody: "Вечер под Лозанной: те же спикеры, главное из программы за раз. Площадку выбираем, запишись — позовём первым.",
  endLauBtn: "Зарегистрироваться",
  endBaden: "Они выйдут на сцену. Ты будешь в зале?",
  endBadenBody: "14 и 15 ноября в Бадене. Двадцать тем, живые цифры и разговоры, которых обычно не бывает.",
  endBtn: "Хочу на форум"
};
const V_NAV = window.FP_NAV ? window.FP_NAV(null) : [];

function Voices() {
  /* ?day=0 открывает вкладку Лозанны — по этому адресу ведёт пункт меню
     «Лозанна»: своей страницы у вечера пока нет. */
  const [day, setDay] = useState(function () {
    const q = new URLSearchParams(location.search).get("day");
    return q && ["all", "0", "1", "2"].indexOf(q) !== -1 ? q : "all";
  });
  const [openId, setOpenId] = useState(null);
  const all = window.FP_VOICES || [];
  const list = window.FP_SORT_V(all.filter((p) => inDay(p, day)), day);
  const open = openId ? all.find((p) => p.id === openId) : null;

  return (
    <div className="kit">
      <div className="fp-backdrop"></div>
      <div className="fp-glow fp-glow--violet"></div>
      <div className="fp-glow fp-glow--amber"></div>

      <TopBar links={V_NAV} />

      <header className="hero gridbg vc-hero">
        <div className="inner">
          <Eyebrow>Frankenplatz · 2026</Eyebrow>
          <h1 className="vc-h1">Голоса форума</h1>
          <p className="sub vc-lede"><Tx>{COPY.lede}</Tx></p>
          <ul className="vc-dates">
            {[0, 1, 2].map((d) =>
            <li key={d}><span className="vc-dates__d">{VDAYS[d].full}</span><span className="vc-dates__p"><Tx>{d === 0 ? COPY.lau : VDAYS[d].sub}</Tx></span></li>)}
          </ul>
        </div>
      </header>

      <section className="vc-sec">
        <div className="inner">
          <h2 className="vc-h2">Кто выступает</h2>
          <ul className="vc-days" role="tablist" aria-label="Дни форума">
            {TABS.map(([k, l]) =>
            <li key={k}><button type="button" role="tab" aria-selected={day === k} className={day === k ? "is-on" : ""} onClick={() => setDay(k)}><span className="vc-days__l"><Tx>{l}</Tx></span><span className="vc-days__m">{TAB_M[k]}</span></button></li>)}
          </ul>
          {day === "0" ?
          <p className="vc-lau">
            <span><Tx>{COPY.lauNote}</Tx></span>
            <a className="fp-btn fp-btn--ghost" href="#reg"
               data-fp-reg-title={COPY.lauReg} data-fp-reg-sub={COPY.lauRegSub}
               data-fp-reg-day={COPY.lauReg} data-fp-reg-btn={COPY.lauBtn} data-fp-reg-role="Лист ожидания · Лозанна"><Tx>{COPY.lauBtn}</Tx></a>
          </p> : null}
          {list.length ?
          <ul className="vc-grid">{list.map((p) => <Tile key={p.id} p={p} day={day} onOpen={() => setOpenId(p.id)} />)}</ul> :
          <p className="vc-empty"><Tx>{COPY.empty}</Tx></p>}
          {/* Куда идти дальше. Стоит сразу под карточками, а не в самом низу:
              до подвала долистывают не все. Вкладка Лозанны ведёт в лист
              ожидания — билетов на тот вечер ещё нет. */}
          <div id="reg" className="vc-cta">
            <ReliefBand variant="final"
              eyebrow={<Eyebrow center>Frankenplatz 2026</Eyebrow>}
              title={day === "0" ? typo(COPY.endLau) : typo(COPY.endBaden)}>
              <p className="fp-band__body"><Tx>{day === "0" ? COPY.endLauBody : COPY.endBadenBody}</Tx></p>
              {day === "0" ?
              <a className="fp-btn fp-btn--gold" href="#reg"
                 data-fp-reg-title={COPY.lauReg} data-fp-reg-sub={COPY.lauRegSub}
                 data-fp-reg-day={COPY.lauReg} data-fp-reg-btn={COPY.lauBtn} data-fp-reg-role="Лист ожидания · Лозанна"><Tx>{COPY.endLauBtn}</Tx></a> :
              <Button variant="gold" href="/tickets">{COPY.endBtn}</Button>}
            </ReliefBand>
          </div>
          <p className="vc-hint"><span><b><Tx>{COPY.hintB}</Tx></b> <Tx>{COPY.hint}</Tx></span></p>
        </div>
      </section>

      <Footer />
      {open ? <Sheet p={open} onClose={() => setOpenId(null)} /> : null}
    </div>);
}

window.Voices = Voices;
