// Luminara — Ecosystems explorer & Tools & Journal
const { useState: useState_e, useRef: useRef_e, useEffect: useEffect_e } = React;

function Ecosystems({ t, locale, onPick }) {
  const { ECOSYSTEMS, BLOCK_TOPICS } = window.LUMINARA_DATA;
  const [openEco, setOpenEco] = useState_e(() =>
    (typeof window !== "undefined" && window.LUM_ROUTE) ? window.LUM_ROUTE.get("e") : null);
  // NAV-RESTRUCTURE A4: which cards are expanded to show their subtopic tree inline.
  const [expanded, setExpanded] = useState_e({});
  const toggleTree = (id, e) => { if (e) e.stopPropagation(); setExpanded(s => ({ ...s, [id]: !s[id] })); };
  const treeLbl = { en: "Subtopics", ru: "Подразделы", uk: "Підрозділи", kk: "Бөлімшелер",
    uz: "Bo'limlar", es: "Subtemas", fr: "Sous-thèmes", hy: "Ենթաթեմաներ" };
  // URL-SUBSTATE: keep the open ecosystem in the hash (#/ecosystems?e=<id>).
  useEffect_e(() => {
    if (typeof window !== "undefined" && window.LUM_ROUTE) window.LUM_ROUTE.set("e", openEco);
  }, [openEco]);

  if (openEco) {
    const eco = ECOSYSTEMS.find(e => e.id === openEco);
    if (eco) return <EcosystemPage t={t} locale={locale} eco={eco}
                          onBack={() => setOpenEco(null)}
                          onAtlas={() => onPick(eco.id)} />;
    // stale/hidden id from a deep link → fall through to the list
  }

  return (
    <div className="section-pad fade-in">
      <div className="section-head">
        <div className="kicker">{t.nav.ecosystems} · 02 / 05</div>
        <h2>{t.ecosystemsTitle}</h2>
        <p>{t.ecosystemsLede}</p>
      </div>

      <div className="eco-grid">
        {ECOSYSTEMS.map((e) => {
          const subs = BLOCK_TOPICS[e.id] || [];
          const isOpen = !!expanded[e.id];
          return (
          <div key={e.id} className="eco-card" data-c={e.id} onClick={() => setOpenEco(e.id)}>
            <div className="head">
              <div className="title">{(e.titleMl && (e.titleMl[locale] || e.titleMl.en)) || e.title}</div>
              <span className="mono" style={{ color: 'currentColor' }}>{`/${e.id}`}</span>
            </div>
            <div className="blurb">{e.blurb[locale] || e.blurb.en}</div>
            <div className="stats">
              {e.stats.map(s => (
                <div className="stat" key={s.k}>
                  <div className="k">{s.k}</div>
                  <div className="v">{s.v}</div>
                  <div className="t">{s.t}</div>
                </div>
              ))}
            </div>
            <div className="protos">
              {e.protocols.map(p => <span key={p} className="proto">{p}</span>)}
            </div>

            {subs.length > 0 && (
              <div className="eco-tree">
                <button className={"eco-tree-toggle" + (isOpen ? " open" : "")}
                        onClick={(ev) => toggleTree(e.id, ev)}
                        aria-expanded={isOpen}>
                  <span className="chev">▸</span>
                  {(treeLbl[locale] || treeLbl.en)} · {subs.length}
                </button>
                {isOpen && (
                  <div className="eco-tree-list">
                    {subs.map((topic, i) => (
                      <div className="eco-tree-item" key={i}
                           onClick={(ev) => { ev.stopPropagation(); onPick && onPick(e.id); }}>
                        <span className="eco-tree-n">{String(i + 1).padStart(2, "0")}</span>
                        <span className="eco-tree-label">{topic}</span>
                      </div>
                    ))}
                  </div>
                )}
              </div>
            )}

            <div className="open">{t.explore} <span className="arr">→</span></div>
          </div>
          );
        })}
      </div>
    </div>
  );
}

function EcosystemPage({ t, locale, eco, onBack, onAtlas }) {
  const { BLOCK_TOPICS } = window.LUMINARA_DATA;
  const topics = BLOCK_TOPICS[eco.id] || [];
  const L = (o) => (o ? (o[locale] || o.en) : "");
  const ui = {
    back:   { en: "All ecosystems", ru: "Все экосистемы", uk: "Усі екосистеми", kk: "Барлық экожүйелер", uz: "Barcha ekotizimlar", es: "Todos los ecosistemas", fr: "Tous les écosystèmes", hy: "Բոլոր էկոհամակարգերը" },
    topics: { en: "Topics in this block", ru: "Темы блока", uk: "Теми блоку", kk: "Блок тақырыптары", uz: "Blok mavzulari", es: "Temas de este bloque", fr: "Thèmes de ce bloc", hy: "Այս բլոկի թեմաները" },
    protos: { en: "Key protocols & tools", ru: "Ключевые протоколы и инструменты", uk: "Ключові протоколи й інструменти", kk: "Негізгі протоколдар мен құралдар", uz: "Asosiy protokollar va vositalar", es: "Protocolos y herramientas clave", fr: "Protocoles et outils clés", hy: "Հիմնական արձանագրություններ և գործիքներ" },
    openAtlas: { en: "Open in atlas", ru: "Открыть в атласе", uk: "Відкрити в атласі", kk: "Атластан ашу", uz: "Atlasda ochish", es: "Abrir en el atlas", fr: "Ouvrir dans l'atlas", hy: "Բացել ատլասում" },
    min:    { en: "min", ru: "мин", uk: "хв", kk: "мин", uz: "daq", es: "min", fr: "min", hy: "րոպե" },
  };
  return (
    <div className="section-pad fade-in eco-page" data-c={eco.id}>
      <button className="eco-back" onClick={onBack}>
        <span>←</span> {L(ui.back)}
      </button>

      <div className="eco-hero">
        <div className="eco-hero-main">
          <div className="kicker" style={{ color: 'currentColor' }}>{`/${eco.id}`}</div>
          <h2 className="eco-hero-title">{(eco.titleMl && (eco.titleMl[locale] || eco.titleMl.en)) || eco.title}</h2>
          <p className="eco-hero-blurb">{L(eco.blurb)}</p>
          <button className="eco-atlas-btn" onClick={onAtlas}>{L(ui.openAtlas)} <span className="arr">→</span></button>
        </div>
        <div className="eco-hero-stats">
          {eco.stats.map(s => (
            <div className="eco-hs" key={s.k}>
              <div className="v">{s.v}</div>
              <div className="k">{s.k}</div>
              <div className="t">{s.t}</div>
            </div>
          ))}
        </div>
      </div>

      <div className="eco-section-h">{L(ui.topics)} · {topics.length}</div>
      <div className="eco-topics">
        {topics.map((topic, i) => (
          <div className="eco-topic" key={i} role="button" tabIndex={0}
               onClick={() => onAtlas && onAtlas()}
               onKeyDown={(e) => { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); onAtlas && onAtlas(); } }}
               style={{ cursor: "pointer" }}>
            <span className="eco-topic-n">{String(i + 1).padStart(2, "0")}</span>
            <span className="eco-topic-label">{topic}</span>
            <span className="eco-topic-go">→</span>
          </div>
        ))}
      </div>

      <div className="eco-section-h">{L(ui.protos)}</div>
      <div className="eco-protos">
        {eco.protocols.map(p => <span key={p} className="eco-proto">{p}</span>)}
      </div>
    </div>
  );
}

function Tools({ t }) {
  const { TOOLS } = window.LUMINARA_DATA;
  // TOOLS-LOCALE: string-safe resolver. tag/cat/stats.k may be a plain string
  // (legacy RU data) or an {ml} object; render the current locale, fall back en->ru.
  const loc = (t && t.code ? t.code.toLowerCase() : "ru");
  const LT = (x) => (typeof x === "string" ? x : (x && (x[loc] || x.en || x.ru)) || "");
  const tabs = [
    ["universal", t.toolsUniversal],
    ["ton",       t.toolsTon],
    ["ethereum",  t.toolsEthereum],
    ["bitcoin",   t.toolsBitcoin],
    ["rwa",       t.toolsRwa],
    ["gamefi",    t.toolsGamefi],
  ];
  const [tab, setTab] = useState_e("universal");

  // Default order. "universal" uses an explicit list (exchange→its wallet pairs first,
  // analytics below); other tabs fall back to colour hue (rainbow). TICKET-020.
  const EXPLICIT_ORDER = {
    universal: [
      "OKX", "OKX Web3 Wallet",
      "CoinGecko", "CoinMarketCap",
    ],
  };
  const defaultOrder = (key) => {
    const arr = (TOOLS[key] || []).slice();
    const explicit = EXPLICIT_ORDER[key];
    if (explicit) {
      const rank = (name) => {
        const i = explicit.indexOf(name);
        return i === -1 ? explicit.length : i;
      };
      arr.sort((a, b) => {
        const ra = rank(a.name), rb = rank(b.name);
        if (ra !== rb) return ra - rb;           // explicit positions first, in order
        return (a.hue ?? 0) - (b.hue ?? 0);      // the rest by hue
      });
    } else {
      arr.sort((a, b) => (a.hue ?? 0) - (b.hue ?? 0));
    }
    return arr.map(x => x.name);
  };

  // Per-tab order of tool names, persisted. User can drag to reorder.
  const [order, setOrder] = useState_e(() => {
    try {
      const saved = JSON.parse(localStorage.getItem("lum-tool-order") || "{}");
      return saved && typeof saved === "object" ? saved : {};
    } catch { return {}; }
  });
  useEffect_e(() => {
    try { localStorage.setItem("lum-tool-order", JSON.stringify(order)); } catch {}
  }, [order]);

  const baseList = TOOLS[tab] || [];
  const names = (order[tab] && order[tab].length) ? order[tab] : defaultOrder(tab);
  // Resolve names → tool objects; drop stale names, append any new tools at the end.
  const byName = {}; baseList.forEach(x => { byName[x.name] = x; });
  const list = names.map(n => byName[n]).filter(Boolean);
  baseList.forEach(x => { if (!names.includes(x.name)) list.push(x); });

  const dragName = useRef_e(null);
  const [dragging, setDragging] = useState_e(null);
  const [overName, setOverName] = useState_e(null);

  const commitOrder = (newNames) => setOrder(o => ({ ...o, [tab]: newNames }));

  const onDrop = (targetName) => {
    const from = dragName.current;
    if (!from || from === targetName) { setDragging(null); setOverName(null); return; }
    const cur = list.map(x => x.name);
    const fromIdx = cur.indexOf(from);
    const toIdx = cur.indexOf(targetName);
    if (fromIdx === -1 || toIdx === -1) return;
    cur.splice(toIdx, 0, cur.splice(fromIdx, 1)[0]);
    commitOrder(cur);
    dragName.current = null; setDragging(null); setOverName(null);
  };

  const resetOrder = () => setOrder(o => { const n = { ...o }; delete n[tab]; return n; });
  const isCustom = !!(order[tab] && order[tab].length);

  const open = (url) => {
    const full = url.startsWith("@") ? "https://t.me/" + url.slice(1) : "https://" + url;
    try { if (window.Telegram?.WebApp?.openLink) { window.Telegram.WebApp.openLink(full); return; } } catch (e) {}
    window.open(full, "_blank", "noopener");
  };

  return (
    <div className="section-pad fade-in">
      <div className="section-head">
        <div className="kicker">{t.nav.tools} · 03 / 05</div>
        <h2>{t.toolsTitle}</h2>
        <p>{t.toolsLede}</p>
      </div>

      <div className="tool-tabs">
        {tabs.map(([k, label]) => (
          <button key={k} className={tab === k ? "active" : ""} onClick={() => setTab(k)}>{label}</button>
        ))}
      </div>

      <div className="tool-reorder-hint">
        <span>⠿ {t.toolsReorder || "Drag cards to arrange your shelf"}</span>
        {isCustom && (
          <button className="tool-reset" onClick={resetOrder}>{t.toolsReset || "Reset"}</button>
        )}
      </div>

      <div className="tool-showcase">
        {list.map((tool) => (
          <div key={tool.name}
               className={"tool-card" + (dragging === tool.name ? " dragging" : "") + (overName === tool.name ? " drag-over" : "")}
               style={{ "--brand": tool.color }}
               draggable
               onDragStart={(e) => { dragName.current = tool.name; setDragging(tool.name); try { e.dataTransfer.effectAllowed = "move"; } catch (x) {} }}
               onDragEnd={() => { setDragging(null); setOverName(null); }}
               onDragOver={(e) => { e.preventDefault(); if (overName !== tool.name) setOverName(tool.name); }}
               onDragLeave={() => { if (overName === tool.name) setOverName(null); }}
               onDrop={(e) => { e.preventDefault(); onDrop(tool.name); }}>
            <span className="tc-grip" title="Drag" aria-hidden="true">⠿</span>
            <div className="bar" />
            <a className="tc-head"
               href={tool.url.startsWith("@") ? "https://t.me/" + tool.url.slice(1) : "https://" + tool.url}
               target="_blank" rel="noopener noreferrer" draggable={false}
               style={{ textDecoration: "none", color: "inherit" }}
               aria-label={tool.name + " — открыть ↗"}
               onClick={(e) => { e.preventDefault(); open(tool.url); }}>
              <div className="tc-mark">{tool.name.charAt(0)}</div>
              <div className="tc-id">
                <div className="tc-name">{tool.name}</div>
                <div className="tc-url">{tool.url}</div>
              </div>
              <span className="tc-go" aria-hidden="true">↗</span>
            </a>
            {tool.cat && <span className="tc-cat">{LT(tool.cat)}</span>}
            <div className="tc-tag" onClick={() => open(tool.url)}>{LT(tool.tag)}</div>
            {tool.stats && (
              <div className="tc-stats">
                {tool.stats.map((s, j) => (
                  <div className="tc-stat-item" key={j}>
                    <div className="tc-sv">{s.v}</div>
                    <div className="tc-sk">{LT(s.k)}</div>
                  </div>
                ))}
              </div>
            )}
          </div>
        ))}
      </div>
    </div>
  );
}

function Journal({ t, locale, onOpenAccount, onJump }) {
  const L = (o) => o[locale] || o.en;

  // MIGRATE-ECOSYSTEMS-SELFCHECK: the former hardcoded "Проверь себя" (3 fixed, ungraded
  // questions) has moved to Directus lum_quiz_questions (topic_key="ecosystems-overview"),
  // read by the same graded <ThemeQuiz> used at the end of every lesson (server-verified,
  // +10 points per correct answer). See TZ-content-quiz-migration.md for the content side.

  const ui = {
    movedTitle: { en: "Your notes live in Account", ru: "Твои заметки — в Личном кабинете", uk: "Твої нотатки — в Особистому кабінеті", kk: "Жазбаларың — Жеке кабинетте", uz: "Qaydlaring Shaxsiy kabinetda", es: "Tus notas están en Tu cuenta", fr: "Tes notes sont dans Ton compte", hy: "Քո նշումները Քո հաշվում են" },
    movedNote: { en: "Saved insights, exploration streak and active attention are now kept together with your profile and wallets.", ru: "Сохранённые инсайты, серия дней исследования и время внимания теперь находятся рядом с профилем и кошельками.", uk: "Збережені інсайти, серія днів дослідження та час уваги тепер поруч із профілем і гаманцями.", kk: "Сақталған инсайттар, зерттеу күндері және назар уақыты енді профиль мен әмияндардың жанында.", uz: "Saqlangan insaytlar, tadqiqot kunlari va eʼtibor vaqti endi profil hamda hamyonlar yonida.", es: "Las ideas guardadas, la racha de exploración y el tiempo de atención ahora están junto a tu perfil y billeteras.", fr: "Les idées sauvegardées, la série d'exploration et le temps d'attention sont désormais près de ton profil et de tes portefeuilles.", hy: "Պահված գաղափարները, ուսումնասիրության շարքը և ուշադրության ժամանակն այժմ քո պրոֆիլի ու դրամապանակների կողքին են։" },
    openAccount: { en: "Open account", ru: "Открыть кабинет", uk: "Відкрити кабінет", kk: "Кабинетті ашу", uz: "Kabinetni ochish", es: "Abrir cuenta", fr: "Ouvrir le compte", hy: "Բացել հաշիվը" },
  };

  return (
    <div className="section-pad fade-in">
      <div className="section-head">
        <div className="kicker">{t.nav.journal} · 04 / 05</div>
        <h2>{t.journalTitle}</h2>
        <p>{t.journalLede}</p>
      </div>

      <div className="journal-moved">
        <div className="kicker">{L(ui.movedTitle)}</div>
        <p>{L(ui.movedNote)}</p>
        <button className="btn" onClick={onOpenAccount}>{L(ui.openAccount)} →</button>
      </div>

      <div className="check-block">
        {window.ThemeQuiz ? <window.ThemeQuiz topic="ecosystems-overview" locale={locale} onJump={onJump} /> : null}
      </div>
    </div>
  );
}

window.Ecosystems = Ecosystems;
window.Tools = Tools;
window.Journal = Journal;

// ── Account / Кабинет ──
let luminaraTonConnectUI = null;
function getLuminaraTonConnectUI(locale) {
  const kit = (typeof window !== "undefined") ? window.TON_CONNECT_UI : null;
  if (!kit || !kit.TonConnectUI) throw new Error("ton_connect_unavailable");
  if (!luminaraTonConnectUI) {
    luminaraTonConnectUI = new kit.TonConnectUI({
      manifestUrl: window.location.origin + "/tonconnect-manifest.json",
      buttonRootId: null,
      enableAndroidBackHandler: false,
      analytics: { mode: "off" },
    });
  }
  const themeName = document.documentElement.getAttribute("data-theme") === "light" ? "LIGHT" : "DARK";
  const theme = (kit.THEME && kit.THEME[themeName]) || themeName;
  luminaraTonConnectUI.uiOptions = {
    language: locale === "ru" ? "ru" : "en",
    uiPreferences: { theme, borderRadius: "s" },
  };
  return luminaraTonConnectUI;
}

function Account({ t, locale, me, learning, onOpenMissions, onOpenInsights, onOpenUniverse, onProfileChanged }) {
  const L = (o) => o[locale] || o.en;
  const ui = {
    title:    { en: "Your account", ru: "Личный кабинет", uk: "Особистий кабінет", kk: "Жеке кабинет", uz: "Shaxsiy kabinet", es: "Tu cuenta", fr: "Ton compte", hy: "Քո հաշիվը" },
    lede:     { en: "Profile, referrals, access, missions and insights in one place.", ru: "Профиль, рефералы, доступ, миссии и инсайты в одном месте.", uk: "Профіль, реферали, доступ, місії та інсайти в одному місці.", kk: "Профиль, рефералдар, қолжетімділік, миссиялар және инсайттар бір жерде.", uz: "Profil, referallar, kirish, missiyalar va insaytlar bir joyda.", es: "Perfil, referidos, acceso, misiones e ideas en un solo lugar.", fr: "Profil, parrainages, accès, missions et idées au même endroit.", hy: "Պրոֆիլ, հրավերներ, հասանելիություն, առաքելություններ և գաղափարներ՝ մեկ տեղում։" },
    profile:  { en: "Profile", ru: "Профиль", uk: "Профіль", kk: "Профиль", uz: "Profil", es: "Perfil", fr: "Profil", hy: "Պրոֆիլ" },
    wallet:   { en: "Connected", ru: "Подключено", uk: "Підключено", kk: "Қосылған", uz: "Ulangan", es: "Conectado", fr: "Connecté", hy: "Միացված է" },
    notConnected: { en: "No wallet connected yet", ru: "Кошелёк ещё не подключён", uk: "Гаманець ще не підключено", kk: "Әмиян әлі жалғанбаған", uz: "Hamyon hali ulanmagan", es: "Aún no hay billetera conectada", fr: "Aucun portefeuille connecté pour l'instant", hy: "Դեռ դրամապանակ միացված չէ" },
    name:     { en: "How to address you", ru: "Как к тебе обращаться", uk: "Як до тебе звертатися", kk: "Саған қалай жүгінейік", uz: "Sizga qanday murojaat qilaylik", es: "Cómo dirigirnos a ti", fr: "Comment t'appeler", hy: "Ինչպես դիմել քեզ" },
    namePh:   { en: "Your name", ru: "Ваше имя", uk: "Ваше ім'я", kk: "Атыңыз", uz: "Ismingiz", es: "Tu nombre", fr: "Ton nom", hy: "Քո անունը" },
    avatarHint: { en: "Change photo", ru: "Сменить фото", uk: "Змінити фото", kk: "Фотоны өзгерту", uz: "Suratni o'zgartirish", es: "Cambiar foto", fr: "Changer la photo", hy: "Փոխել լուսանկարը" },
    saveShort:  { en: "Save", ru: "Сохранить", uk: "Зберегти", kk: "Сақтау", uz: "Saqlash", es: "Guardar", fr: "Enregistrer", hy: "Պահել" },
    savedShort: { en: "✓ Saved", ru: "✓ Сохранено", uk: "✓ Збережено", kk: "✓ Сақталды", uz: "✓ Saqlandi", es: "✓ Guardado", fr: "✓ Enregistré", hy: "✓ Պահված է" },
    logout:     { en: "Log out", ru: "Выйти", uk: "Вийти", kk: "Шығу", uz: "Chiqish", es: "Cerrar sesión", fr: "Se déconnecter", hy: "Դուրս գալ" },
    switchAcct: { en: "Switch account", ru: "Войти другим аккаунтом", uk: "Увійти іншим акаунтом", kk: "Басқа аккаунтпен кіру", uz: "Boshqa akkaunt bilan kirish", es: "Cambiar de cuenta", fr: "Changer de compte", hy: "Փոխել հաշիվը" },
    switchNote: { en: "Telegram may return the same account. To use a different identity, log in to a different Telegram first.", ru: "Telegram может вернуть тот же аккаунт. Для другой личности сначала войдите в другой Telegram.", uk: "Telegram може повернути той самий акаунт. Для іншої особи спершу увійдіть в інший Telegram.", kk: "Telegram сол аккаунтты қайтаруы мүмкін. Басқа тұлға үшін алдымен басқа Telegram-ға кіріңіз.", uz: "Telegram avvalgi akkauntni qaytarishi mumkin. Boshqa shaxs uchun avval boshqa Telegramga kiring.", es: "Telegram puede devolver la misma cuenta. Para otra identidad, inicia sesión en otro Telegram primero.", fr: "Telegram peut renvoyer le même compte. Pour changer, connectez-vous d’abord à un autre Telegram.", hy: "Տելեգրամը կարող է վերադարձնել նույն հաշիվը։ Այլ հաշվի համար նախ մուտք գործեք այլ Telegram։" },
    switchConfirm: { en: "Log out and switch", ru: "Выйти и сменить", uk: "Вийти й змінити", kk: "Шығу және ауыстыру", uz: "Chiqish va almashtirish", es: "Cerrar sesión y cambiar", fr: "Se déconnecter et changer", hy: "Դուրս գալ և փոխել" },
    bday:     { en: "Birthday (optional)", ru: "Дата рождения (необязательно)", uk: "Дата народження (необов’язково)", kk: "Туған күн (міндетті емес)", uz: "Tugʻilgan kun (ixtiyoriy)", es: "Fecha de nacimiento (opcional)", fr: "Date de naissance (facultatif)", hy: "Ծննդյան օր (ըստ ցանկության)" },
    bdayNote: { en: "You can change or delete it at any time.", ru: "Её можно изменить или удалить в любой момент.", uk: "Її можна змінити або видалити будь-коли.", kk: "Оны кез келген уақытта өзгертуге немесе жоюға болады.", uz: "Uni istalgan payt o‘zgartirish yoki o‘chirish mumkin.", es: "Puedes cambiarla o eliminarla cuando quieras.", fr: "Tu peux la modifier ou la supprimer à tout moment.", hy: "Կարող ես ցանկացած պահին փոխել կամ ջնջել։" },
    clear:    { en: "Clear", ru: "Удалить", uk: "Видалити", kk: "Жою", uz: "O‘chirish", es: "Borrar", fr: "Effacer", hy: "Ջնջել" },
    learning: { en: "Learning progress", ru: "Прогресс обучения", uk: "Прогрес навчання", kk: "Оқу барысы", uz: "Ta’lim jarayoni", es: "Progreso de aprendizaje", fr: "Progression d’apprentissage", hy: "Ուսուցման առաջընթաց" },
    chaptersDone: { en: "chapters completed", ru: "глав пройдено", uk: "глав пройдено", kk: "тарау аяқталды", uz: "bob yakunlandi", es: "capítulos completados", fr: "chapitres terminés", hy: "գլուխ ավարտված է" },
    quizAverage: { en: "Average quiz result", ru: "Средний результат квизов", uk: "Середній результат квізів", kk: "Квиздердің орташа нәтижесі", uz: "Kvizlarning o‘rtacha natijasi", es: "Resultado medio de los quizzes", fr: "Résultat moyen aux quiz", hy: "Քվիզների միջին արդյունքը" },
    noQuizYet: { en: "No quiz answers yet", ru: "Квизы ещё не пройдены", uk: "Квізи ще не пройдені", kk: "Квиздер әлі өтпеген", uz: "Kvizlar hali topshirilmagan", es: "Aún no hay respuestas", fr: "Aucune réponse pour le moment", hy: "Քվիզներ դեռ չեն անցել" },
    status:   { en: "Rating", ru: "Рейтинг", uk: "Рейтинг", kk: "Рейтинг", uz: "Reyting", es: "Clasificación", fr: "Classement", hy: "Վարկանիշ" },
    free:     { en: "Without subscription · Explorer", ru: "Без подписки · Explorer", uk: "Без підписки · Explorer", kk: "Жазылымсыз · Explorer", uz: "Obunasiz · Explorer", es: "Sin suscripción · Explorador", fr: "Sans abonnement · Explorateur", hy: "Առանց բաժանորդագրության · Հետազոտող" },
    toNext:   { en: "to next level", ru: "до следующего уровня", uk: "до наступного рівня", kk: "келесі деңгейге дейін", uz: "keyingi darajagacha", es: "para el siguiente nivel", fr: "jusqu'au niveau suivant", hy: "մինչև հաջորդ մակարդակ" },
    points:   { en: "points", ru: "баллов", uk: "балів", kk: "ұпай", uz: "ball", es: "puntos", fr: "points", hy: "միավոր" },
    referral: { en: "Referrals", ru: "Реферальная программа", uk: "Реферальна програма", kk: "Реферал бағдарламасы", uz: "Referal dasturi", es: "Referidos", fr: "Parrainages", hy: "Հրավերներ" },
    yourLink: { en: "Your invite link", ru: "Твоя инвайт-ссылка", uk: "Твоє інвайт-посилання", kk: "Сенің инвайт-сілтемең", uz: "Sening invayt-havolang", es: "Tu enlace de invitación", fr: "Ton lien d'invitation", hy: "Քո հրավերի հղումը" },
    copy:     { en: "Copy", ru: "Копировать", uk: "Копіювати", kk: "Көшіру", uz: "Nusxa olish", es: "Copiar", fr: "Copier", hy: "Պատճենել" },
    copied:   { en: "Copied", ru: "Скопировано", uk: "Скопійовано", kk: "Көшірілді", uz: "Nusxa olindi", es: "Copiado", fr: "Copié", hy: "Պատճենված է" },
    haveCode: { en: "Have an invite code?", ru: "Есть инвайт-код?", uk: "Є інвайт-код?", kk: "Инвайт-код бар ма?", uz: "Invayt-kod bormi?", es: "¿Tienes un código de invitación?", fr: "Tu as un code d'invitation ?", hy: "Հրավերի կո՞դ ունես" },
    codePh:   { en: "Enter code", ru: "Введи код", uk: "Введи код", kk: "Кодты енгіз", uz: "Kodni kirit", es: "Ingresa el código", fr: "Saisis le code", hy: "Մուտքագրիր կոդը" },
    apply:    { en: "Apply", ru: "Применить", uk: "Застосувати", kk: "Қолдану", uz: "Qoʻllash", es: "Aplicar", fr: "Appliquer", hy: "Կիրառել" },
    invited:  { en: "invited", ru: "приглашено", uk: "запрошено", kk: "шақырылды", uz: "taklif qilindi", es: "invitados", fr: "invités", hy: "հրավիրված" },
    pending:  { en: "pending", ru: "ожидают", uk: "очікують", kk: "күтуде", uz: "kutilmoqda", es: "pendientes", fr: "en attente", hy: "սպասման մեջ" },
    converted:{ en: "activated", ru: "активировано", uk: "активовано", kk: "белсендірілді", uz: "faollashtirildi", es: "activados", fr: "activés", hy: "ակտիվացված" },
    awarded:  { en: "awarded", ru: "награждено", uk: "нагороджено", kk: "марапатталды", uz: "taqdirlandi", es: "premiados", fr: "récompensés", hy: "պարգևատրված" },
    earned:   { en: "awarded points", ru: "начислено баллов", uk: "нараховано балів", kk: "есептелген ұпай", uz: "hisoblangan ball", es: "puntos otorgados", fr: "points attribués", hy: "շնորհված միավոր" },
    sub:      { en: "Access", ru: "Доступ и подписка", uk: "Доступ і підписка", kk: "Қолжетімділік және жазылым", uz: "Kirish va obuna", es: "Acceso", fr: "Accès", hy: "Հասանելիություն" },
    subNote:  { en: "Foundations are available without subscription. The full atlas opens with a subscription.", ru: "Основания доступны без подписки. Полный атлас открывается с подпиской.", uk: "Основи доступні без підписки. Повний атлас відкривається з підпискою.", kk: "Негіздер жазылымсыз қолжетімді. Толық атлас жазылыммен ашылады.", uz: "Asoslar obunasiz mavjud. Toʻliq atlas obuna bilan ochiladi.", es: "Los fundamentos están disponibles sin suscripción. El atlas completo se abre con suscripción.", fr: "Les fondations sont accessibles sans abonnement. L’atlas complet s’ouvre avec un abonnement.", hy: "Հիմքերը հասանելի են առանց բաժանորդագրության։ Ամբողջ ատլասը բացվում է բաժանորդագրությամբ։" },
    missions: { en: "Your missions", ru: "Твои миссии", uk: "Твої місії", kk: "Сенің миссияларың", uz: "Sening missiyalaring", es: "Tus misiones", fr: "Tes missions", hy: "Քո առաքելությունները" },
    missionNote:{ en: "Each step makes you a little better. Points raise your rating.", ru: "Каждый шаг делает тебя чуть лучше. Баллы поднимают твой рейтинг.", uk: "Кожен крок робить тебе трохи кращим. Бали піднімають твій рейтинг.", kk: "Әр қадам сені сәл жақсартады. Ұпай рейтингіңді көтереді.", uz: "Har bir qadam seni biroz yaxshilaydi. Ballar reytingingni koʻtaradi.", es: "Cada paso te hace un poco mejor. Los puntos suben tu clasificación.", fr: "Chaque étape te rend un peu meilleur. Les points augmentent ton classement.", hy: "Յուրաքանչյուր քայլ քեզ մի փոքր ավելի լավն է դարձնում։ Միավորները բարձրացնում են վարկանիշդ։" },
    allMissions:{ en: "All missions", ru: "Все миссии", uk: "Усі місії", kk: "Барлық миссиялар", uz: "Barcha missiyalar", es: "Todas las misiones", fr: "Toutes les missions", hy: "Բոլոր առաքելությունները" },
    insights:  { en: "Your insights", ru: "Твои инсайты", uk: "Твої інсайти", kk: "Сенің инсайттарың", uz: "Sening insaytlaring", es: "Tus ideas", fr: "Tes idées", hy: "Քո գաղափարները" },
    insightNote:{ en: "After every lesson — capture what struck you. Share it if you want.", ru: "После каждого урока — запиши, что зацепило. Поделись, если захочешь.", uk: "Після кожного уроку — запиши, що зачепило. Поділись, якщо захочеш.", kk: "Әр сабақтан кейін — не әсер еткенін жаз. Қаласаң, бөліс.", uz: "Har bir darsdan keyin — nima taʼsir qilganini yoz. Xohlasang, ulash.", es: "Después de cada lección, anota lo que te impactó. Compártelo si quieres.", fr: "Après chaque leçon — note ce qui t'a marqué. Partage-le si tu veux.", hy: "Յուրաքանչյուր դասից հետո՝ գրիր, ինչը քեզ տպավորեց։ Կիսվիր, եթե ուզում ես։" },
    allInsights:{ en: "All insights", ru: "Все инсайты", uk: "Усі інсайти", kk: "Барлық инсайттар", uz: "Barcha insaytlar", es: "Todas las ideas", fr: "Toutes les idées", hy: "Բոլոր գաղափարները" },
    insightCount: { en: "saved insights", ru: "сохранённых инсайтов", uk: "збережених інсайтів", kk: "сақталған инсайттар", uz: "saqlangan insaytlar", es: "ideas guardadas", fr: "idées sauvegardées", hy: "պահված գաղափարներ" },
    streak: { en: "exploration streak", ru: "серия дней исследования", uk: "серія днів дослідження", kk: "зерттеу күндерінің сериясы", uz: "tadqiqot kunlari seriyasi", es: "racha de exploración", fr: "série d'exploration", hy: "ուսումնասիրման շարք" },
    days: { en: "days", ru: "дней", uk: "днів", kk: "күн", uz: "kun", es: "días", fr: "jours", hy: "օր" },
    activeHours: { en: "active attention", ru: "активного внимания", uk: "активної уваги", kk: "белсенді назар", uz: "faol eʼtibor", es: "atención activa", fr: "attention active", hy: "ակտիվ ուշադրություն" },
    activeNote: { en: "time in Luminara", ru: "времени в Luminara", uk: "часу в Luminara", kk: "Luminara-дағы уақыт", uz: "Luminara’dagi vaqt", es: "tiempo en Luminara", fr: "temps sur Luminara", hy: "Luminara-ում անցկացրած ժամանակ" },
    noInsights: { en: "No personal insights yet. Save one after a lesson and it will appear here.", ru: "Пока нет личных инсайтов. Сохрани заметку после урока — она появится здесь.", uk: "Поки немає особистих інсайтів. Збережи нотатку після уроку — вона з’явиться тут.", kk: "Жеке инсайттар әлі жоқ. Сабақтан кейін жазба сақта — ол осында пайда болады.", uz: "Hali shaxsiy insaytlar yoʻq. Darsdan keyin qayd saqla — u shu yerda chiqadi.", es: "Aún no hay ideas personales. Guarda una nota después de una lección y aparecerá aquí.", fr: "Pas encore d'idée personnelle. Sauvegarde une note après une leçon et elle apparaîtra ici.", hy: "Անձնական գաղափարներ դեռ չկան։ Դասից հետո պահիր նշում, և այն կհայտնվի այստեղ։" },
    insightsUnavailable: { en: "Your saved insights could not be loaded. Try again later.", ru: "Не удалось загрузить сохранённые инсайты. Попробуй позже.", uk: "Не вдалося завантажити збережені інсайти. Спробуй пізніше.", kk: "Сақталған инсайттарды жүктеу мүмкін болмады. Кейінірек көр.", uz: "Saqlangan insaytlarni yuklab boʻlmadi. Keyinroq urinib koʻr.", es: "No se pudieron cargar las ideas guardadas. Inténtalo más tarde.", fr: "Impossible de charger les idées sauvegardées. Réessaie plus tard.", hy: "Չհաջողվեց բեռնել պահված գաղափարները։ Փորձիր ավելի ուշ։" },
    sbt:       { en: "Certificates (SBT)", ru: "Сертификаты (SBT)", uk: "Сертифікати (SBT)", kk: "Сертификаттар (SBT)", uz: "Sertifikatlar (SBT)", es: "Certificados (SBT)", fr: "Certificats (SBT)", hy: "Վկայականներ (SBT)" },
    sbtNote:   { en: "Soon: non-transferable on-chain certificates for completed courses — visible on your profile forever.", ru: "Скоро: непередаваемые on-chain сертификаты за пройденные курсы — навсегда в профиле.", uk: "Скоро: непередавані on-chain сертифікати за пройдені курси — назавжди в профілі.", kk: "Жақында: өткен курстар үшін берілмейтін on-chain сертификаттар — профильде мәңгі.", uz: "Tez orada: oʻtilgan kurslar uchun oʻtkazib boʻlmaydigan on-chain sertifikatlar — profilda abadiy.", es: "Pronto: certificados on-chain no transferibles por cursos completados, visibles en tu perfil para siempre.", fr: "Bientôt : des certificats on-chain non transférables pour les cours terminés — visibles sur ton profil pour toujours.", hy: "Շուտով՝ ավարտած դասընթացների համար ոչ փոխանցելի on-chain վկայականներ՝ ընդմիշտ տեսանելի քո պրոֆիլում։" },
    soon:      { en: "soon", ru: "скоро", uk: "скоро", kk: "жақында", uz: "tez orada", es: "pronto", fr: "bientôt", hy: "շուտով" },
    linked:    { en: "Linked logins", ru: "Привязанные входы", uk: "Прив'язані входи", kk: "Байланысқан кірулер", uz: "Bogʻlangan kirishlar", es: "Inicios de sesión vinculados", fr: "Connexions liées", hy: "Կապված մուտքեր" },
    linkBtn:   { en: "Link", ru: "Привязать", uk: "Прив'язати", kk: "Байланыстыру", uz: "Bogʻlash", es: "Vincular", fr: "Lier", hy: "Կապել" },
    linkOk:    { en: "✓ Linked", ru: "✓ Привязано", uk: "✓ Прив'язано", kk: "✓ Байланысты", uz: "✓ Bogʻlandi", es: "✓ Vinculado", fr: "✓ Lié", hy: "✓ Կապված է" },
    linkConflict: { en: "Already linked to another account.", ru: "Уже привязано к другому аккаунту.", uk: "Вже прив'язано до іншого акаунта.", kk: "Басқа аккаунтқа байланысқан.", uz: "Boshqa akkauntga bogʻlangan.", es: "Ya está vinculado a otra cuenta.", fr: "Déjà lié à un autre compte.", hy: "Արդեն կապված է մեկ այլ հաշվի հետ։" },
    linkErr:   { en: "Couldn't link. Try again.", ru: "Не удалось привязать. Попробуй ещё раз.", uk: "Не вдалося прив'язати. Спробуй ще раз.", kk: "Байланыстыру сәтсіз. Қайта көр.", uz: "Bogʻlab boʻlmadi. Yana urinib koʻr.", es: "No se pudo vincular. Inténtalo de nuevo.", fr: "Impossible de lier. Réessaie.", hy: "Չհաջողվեց կապել։ Փորձիր նորից։" },
    noWallet:  { en: "No browser wallet found.", ru: "В браузере не найден кошелёк.", uk: "У браузері не знайдено гаманець.", kk: "Браузерде әмиян табылмады.", uz: "Brauzerda hamyon topilmadi.", es: "No se encontró una billetera en el navegador.", fr: "Aucun portefeuille de navigateur trouvé.", hy: "Բրաուզերի դրամապանակ չգտնվեց։" },
    tgOnly:    { en: "Available inside Telegram.", ru: "Доступно внутри Telegram.", uk: "Доступно всередині Telegram.", kk: "Telegram ішінде қолжетімді.", uz: "Telegram ichida mavjud.", es: "Disponible dentro de Telegram.", fr: "Disponible dans Telegram.", hy: "Հասանելի է Telegram-ում։" },
    mergeOffer: { en: "This login belongs to another account. Merge it into this one?", ru: "Этот вход привязан к другому аккаунту. Объединить с этим?", uk: "Цей вхід належить іншому акаунту. Об'єднати з цим?", kk: "Бұл кіру басқа аккаунтқа тиесілі. Осымен біріктіру керек пе?", uz: "Bu kirish boshqa hisobga tegishli. Shu bilan birlashtirilsinmi?", es: "Este inicio de sesión pertenece a otra cuenta. ¿Fusionarlo con esta?", fr: "Cette connexion appartient à un autre compte. La fusionner avec celui-ci ?", hy: "Այս մուտքը պատկանում է մեկ այլ հաշվի։ Միացնե՞լ սրան։" },
    mergeBtn:  { en: "Merge accounts", ru: "Объединить аккаунты", uk: "Об'єднати акаунти", kk: "Аккаунттарды біріктіру", uz: "Hisoblarni birlashtirish", es: "Fusionar cuentas", fr: "Fusionner les comptes", hy: "Միացնել հաշիվները" },
    mergeOk:   { en: "✓ Accounts merged", ru: "✓ Аккаунты объединены", uk: "✓ Акаунти об'єднано", kk: "✓ Аккаунттар біріктірілді", uz: "✓ Hisoblar birlashtirildi", es: "✓ Cuentas fusionadas", fr: "✓ Comptes fusionnés", hy: "✓ Հաշիվները միացված են" },
    mergeErr:  { en: "Couldn't merge. Try again.", ru: "Не удалось объединить. Попробуй ещё раз.", uk: "Не вдалося об'єднати. Спробуй ще раз.", kk: "Біріктіру сәтсіз. Қайта көр.", uz: "Birlashtirib boʻlmadi. Yana urinib koʻr.", es: "No se pudo fusionar. Inténtalo de nuevo.", fr: "Impossible de fusionner. Réessaie.", hy: "Չհաջողվեց միացնել։ Փորձիր նորից։" },
    mergeCancel: { en: "Not now", ru: "Не сейчас", uk: "Не зараз", kk: "Қазір емес", uz: "Hozir emas", es: "Ahora no", fr: "Pas maintenant", hy: "Հիմա չէ" },
    activationNeeded: { en: "Complete the first scene, answer a quiz correctly and verify a TON wallet to unlock this action.", ru: "Заверши первую сцену, правильно ответь на квиз и подтверди TON-кошелёк, чтобы открыть это действие." },
    walletNeeded: { en: "Verify a TON wallet to receive your invite link.", ru: "Подтверди TON-кошелёк, чтобы получить инвайт-ссылку." },
    manualTon: { en: "Telegram Wallet address", ru: "Адрес Telegram Wallet" },
    manualTonPh: { en: "Paste a TON address", ru: "Вставь TON-адрес" },
    manualTonSave: { en: "Save address", ru: "Сохранить адрес" },
    unverified: { en: "Saved, ownership not verified", ru: "Сохранено, владение не подтверждено" },
    mergeReview: { en: "This merge requires a manual security review.", ru: "Это объединение требует ручной проверки безопасности." },
    activationTitle: { en: "Account activation", ru: "Активация аккаунта", uk: "Активація акаунта" },
    activationScene: { en: "Complete your first scene", ru: "Пройти первую сцену", uk: "Пройти першу сцену" },
    activationQuiz: { en: "Answer one quiz correctly", ru: "Правильно ответить на один квиз", uk: "Правильно відповісти на один квіз" },
    activationTon: { en: "Verify ownership of a TON wallet", ru: "Подтвердить владение TON-кошельком", uk: "Підтвердити володіння TON-гаманцем" },
    activationOpenScene: { en: "Open scene", ru: "Открыть сцену", uk: "Відкрити сцену" },
    activationOpenQuiz: { en: "Open quiz", ru: "Открыть квиз", uk: "Відкрити квіз" },
    activationTonHint: {
      en: "Confirm that the wallet belongs to you. This signs a one-time request: no transfer, fee or access to your funds.",
      ru: "Подтверди, что кошелёк принадлежит тебе. Это подпись одноразового запроса: без перевода, комиссии и доступа к средствам.",
      uk: "Підтвердь, що гаманець належить тобі. Це підпис одноразового запиту: без переказу, комісії та доступу до коштів.",
      kk: "Әмиянның саған тиесілі екенін раста. Бұл бір реттік сұрауға қол қою: аударымсыз, комиссиясыз және қаражатқа қолжетімсіз.",
      uz: "Hamyon senga tegishli ekanini tasdiqla. Bu bir martalik so‘rovni imzolash: pul o‘tkazilmaydi, komissiya yo‘q va mablag‘ga kirilmaydi.",
      es: "Confirma que la billetera te pertenece. Solo firmas una solicitud única: sin transferencia, comisión ni acceso a tus fondos.",
      fr: "Confirme que ce portefeuille t’appartient. Tu signes une demande unique : aucun transfert, frais ni accès à tes fonds.",
      hy: "Հաստատիր, որ դրամապանակը քոնն է։ Սա մեկանգամյա հարցման ստորագրում է՝ առանց փոխանցման, վճարի կամ միջոցներին հասանելիության։",
    },
    tonConnectBtn: { en: "Verify with Tonkeeper", ru: "Подтвердить через Tonkeeper", uk: "Підтвердити через Tonkeeper", kk: "Tonkeeper арқылы растау", uz: "Tonkeeper orqali tasdiqlash", es: "Verificar con Tonkeeper", fr: "Vérifier avec Tonkeeper", hy: "Հաստատել Tonkeeper-ով" },
    tonConnectAny: { en: "Tonkeeper or another TON wallet will open. Check the Luminara name and confirm the connection.", ru: "Откроется Tonkeeper или другой TON-кошелёк. Проверь название Luminara и подтверди подключение.", uk: "Відкриється Tonkeeper або інший TON-гаманець. Перевір назву Luminara й підтвердь підключення.", kk: "Tonkeeper немесе басқа TON әмияны ашылады. Luminara атауын тексеріп, қосылуды раста.", uz: "Tonkeeper yoki boshqa TON hamyon ochiladi. Luminara nomini tekshirib, ulanishni tasdiqla.", es: "Se abrirá Tonkeeper u otra billetera TON. Comprueba el nombre Luminara y confirma la conexión.", fr: "Tonkeeper ou un autre portefeuille TON va s’ouvrir. Vérifie le nom Luminara et confirme la connexion.", hy: "Կբացվի Tonkeeper-ը կամ մեկ այլ TON դրամապանակ։ Ստուգիր Luminara անունը և հաստատիր կապը։" },
    tonWaiting: { en: "Waiting for confirmation in the wallet…", ru: "Ждём подтверждения в кошельке…", uk: "Очікуємо підтвердження в гаманці…", kk: "Әмиянда растауды күтеміз…", uz: "Hamyonda tasdiqlash kutilmoqda…", es: "Esperando confirmación en la billetera…", fr: "En attente de confirmation dans le portefeuille…", hy: "Սպասում ենք դրամապանակում հաստատմանը…" },
    tonVerified: { en: "✓ Wallet ownership verified", ru: "✓ Владение кошельком подтверждено", uk: "✓ Володіння гаманцем підтверджено", kk: "✓ Әмиянға иелік расталды", uz: "✓ Hamyonga egalik tasdiqlandi", es: "✓ Propiedad de la billetera verificada", fr: "✓ Propriété du portefeuille vérifiée", hy: "✓ Դրամապանակի պատկանելիությունը հաստատված է" },
    tonSaved: { en: "TON address saved, ownership not verified", ru: "TON-адрес сохранён, владение не подтверждено", uk: "TON-адресу збережено, володіння не підтверджено", kk: "TON мекенжайы сақталды, иелік расталмаған", uz: "TON manzili saqlandi, egalik tasdiqlanmagan", es: "Dirección TON guardada, propiedad sin verificar", fr: "Adresse TON enregistrée, propriété non vérifiée", hy: "TON հասցեն պահպանված է, սեփականությունը հաստատված չէ" },
    tonCancelled: { en: "Confirmation was cancelled. You can try again.", ru: "Подтверждение отменено. Можно попробовать ещё раз.", uk: "Підтвердження скасовано. Можна спробувати ще раз.", kk: "Растау тоқтатылды. Қайта көруге болады.", uz: "Tasdiqlash bekor qilindi. Qayta urinib ko‘rish mumkin.", es: "La confirmación se canceló. Puedes intentarlo de nuevo.", fr: "La confirmation a été annulée. Tu peux réessayer.", hy: "Հաստատումը չեղարկվեց։ Կարող ես կրկին փորձել։" },
    tonProofErr: { en: "The wallet did not return a valid ownership proof. Update it or choose another TON wallet.", ru: "Кошелёк не вернул корректное подтверждение владения. Обнови его или выбери другой TON-кошелёк.", uk: "Гаманець не повернув коректне підтвердження володіння. Онови його або вибери інший TON-гаманець.", kk: "Әмиян дұрыс иелік дәлелін қайтармады. Оны жаңарт немесе басқа TON әмиянын таңда.", uz: "Hamyon to‘g‘ri egalik isbotini qaytarmadi. Uni yangila yoki boshqa TON hamyonni tanla.", es: "La billetera no devolvió una prueba válida. Actualízala o elige otra billetera TON.", fr: "Le portefeuille n’a pas renvoyé de preuve valide. Mets-le à jour ou choisis un autre portefeuille TON.", hy: "Դրամապանակը վավեր ապացույց չվերադարձրեց։ Թարմացրու այն կամ ընտրիր մեկ այլ TON դրամապանակ։" },
    tonSdkErr: { en: "TON Connect could not start. Check your connection and try again.", ru: "Не удалось запустить TON Connect. Проверь соединение и попробуй ещё раз.", uk: "Не вдалося запустити TON Connect. Перевір з’єднання й спробуй ще раз.", kk: "TON Connect іске қосылмады. Байланысты тексеріп, қайта көр.", uz: "TON Connect ishga tushmadi. Ulanishni tekshirib, qayta urin.", es: "No se pudo iniciar TON Connect. Comprueba la conexión e inténtalo de nuevo.", fr: "Impossible de lancer TON Connect. Vérifie la connexion et réessaie.", hy: "Չհաջողվեց գործարկել TON Connect-ը։ Ստուգիր կապը և կրկին փորձիր։" },
    tonSeedWarning: { en: "Luminara will never ask for your seed phrase.", ru: "Luminara никогда не попросит seed-фразу.", uk: "Luminara ніколи не попросить seed-фразу.", kk: "Luminara ешқашан seed-фразаны сұрамайды.", uz: "Luminara hech qachon seed-iborani so‘ramaydi.", es: "Luminara nunca te pedirá la frase semilla.", fr: "Luminara ne te demandera jamais ta phrase de récupération.", hy: "Luminara-ն երբեք չի խնդրի քո seed phrase-ը։" },
    manualTonNote: { en: "Saving an address does not verify ownership or activate the account.", ru: "Сохранение адреса не подтверждает владение и не активирует аккаунт.", uk: "Збереження адреси не підтверджує володіння й не активує акаунт.", kk: "Мекенжайды сақтау иелікті растамайды және аккаунтты белсендірмейді.", uz: "Manzilni saqlash egalikni tasdiqlamaydi va akkauntni faollashtirmaydi.", es: "Guardar una dirección no verifica la propiedad ni activa la cuenta.", fr: "Enregistrer une adresse ne prouve pas sa propriété et n’active pas le compte.", hy: "Հասցեն պահպանելը չի հաստատում պատկանելիությունը և չի ակտիվացնում հաշիվը։" },
  };

  const refMessages = {
    en: { pending:"Invite saved. Points follow only after activation.", invalid_code:"Code not found.", already:"An invite is already attached to this account.", self:"You cannot use your own code.", expired:"This code has expired.", inactive:"This code is inactive.", max_uses:"This code has reached its usage limit.", test_user:"Test accounts cannot join referrals.", referrer_daily_limit:"This invite link reached today's limit.", ip_daily_limit:"Too many attempts from this network today." },
    ru: { pending:"Инвайт сохранён. Баллы появятся только после активации.", invalid_code:"Код не найден.", already:"К этому аккаунту уже привязан инвайт.", self:"Нельзя применить собственный код.", expired:"Срок действия кода истёк.", inactive:"Код отключён.", max_uses:"Лимит использований кода исчерпан.", test_user:"Тестовые аккаунты не участвуют в рефералах.", referrer_daily_limit:"Сегодняшний лимит этой ссылки исчерпан.", ip_daily_limit:"Сегодня с этой сети было слишком много попыток." },
    uk: { pending:"Інвайт збережено. Бали з'являться лише після активації.", invalid_code:"Код не знайдено.", already:"До цього акаунта вже прив'язано інвайт.", self:"Не можна застосувати власний код.", expired:"Термін дії коду минув.", inactive:"Код вимкнено.", max_uses:"Ліміт використань коду вичерпано.", test_user:"Тестові акаунти не беруть участі в рефералах.", referrer_daily_limit:"Денний ліміт цього посилання вичерпано.", ip_daily_limit:"Сьогодні з цієї мережі було забагато спроб." },
    kk: { pending:"Инвайт сақталды. Ұпай белсендіруден кейін беріледі.", invalid_code:"Код табылмады.", already:"Бұл аккаунтқа инвайт тіркелген.", self:"Өз кодыңызды қолдануға болмайды.", expired:"Кодтың мерзімі аяқталған.", inactive:"Код өшірілген.", max_uses:"Кодты пайдалану лимиті бітті.", test_user:"Тест аккаунттары рефералға қатыспайды.", referrer_daily_limit:"Бұл сілтеменің күндік лимиті бітті.", ip_daily_limit:"Бүгін осы желіден тым көп әрекет жасалды." },
    uz: { pending:"Invayt saqlandi. Ballar faollashgandan keyin beriladi.", invalid_code:"Kod topilmadi.", already:"Bu hisobga invayt allaqachon biriktirilgan.", self:"O'z kodingizni qo'llab bo'lmaydi.", expired:"Kod muddati tugagan.", inactive:"Kod o'chirilgan.", max_uses:"Koddan foydalanish limiti tugagan.", test_user:"Test hisoblari referalda qatnashmaydi.", referrer_daily_limit:"Bu havolaning bugungi limiti tugagan.", ip_daily_limit:"Bugun bu tarmoqdan juda ko'p urinish bo'ldi." },
    es: { pending:"Invitación guardada. Los puntos llegan solo tras la activación.", invalid_code:"No se encontró el código.", already:"Esta cuenta ya tiene una invitación asociada.", self:"No puedes usar tu propio código.", expired:"Este código ha caducado.", inactive:"Este código está inactivo.", max_uses:"Este código alcanzó su límite de usos.", test_user:"Las cuentas de prueba no participan en referidos.", referrer_daily_limit:"Este enlace alcanzó su límite diario.", ip_daily_limit:"Demasiados intentos desde esta red hoy." },
    fr: { pending:"Invitation enregistrée. Les points arrivent seulement après activation.", invalid_code:"Code introuvable.", already:"Une invitation est déjà liée à ce compte.", self:"Tu ne peux pas utiliser ton propre code.", expired:"Ce code a expiré.", inactive:"Ce code est inactif.", max_uses:"Ce code a atteint sa limite d'utilisation.", test_user:"Les comptes de test ne participent pas aux parrainages.", referrer_daily_limit:"Ce lien a atteint sa limite quotidienne.", ip_daily_limit:"Trop de tentatives depuis ce réseau aujourd'hui." },
    hy: { pending:"Հրավերը պահպանված է։ Միավորները կտրվեն միայն ակտիվացումից հետո։", invalid_code:"Կոդը չի գտնվել։", already:"Այս հաշվին արդեն հրավեր է կցված։", self:"Չես կարող օգտագործել սեփական կոդը։", expired:"Կոդի ժամկետը լրացել է։", inactive:"Կոդն անջատված է։", max_uses:"Կոդի օգտագործման սահմանաչափը սպառվել է։", test_user:"Թեստային հաշիվները չեն մասնակցում հրավերներին։", referrer_daily_limit:"Այս հղման օրական սահմանաչափը սպառվել է։", ip_daily_limit:"Այսօր այս ցանցից չափազանց շատ փորձ է եղել։" },
  };

  const plans = [
    { id: "month", label: { en: "1 month", ru: "1 месяц", uk: "1 місяць", kk: "1 ай", uz: "1 oy", es: "1 mes", fr: "1 mois", hy: "1 ամիս" }, price: "$5" },
    { id: "quarter", label: { en: "3 months", ru: "3 месяца", uk: "3 місяці", kk: "3 ай", uz: "3 oy", es: "3 meses", fr: "3 mois", hy: "3 ամիս" }, price: "$12" },
    { id: "half", label: { en: "6 months", ru: "6 месяцев", uk: "6 місяців", kk: "6 ай", uz: "6 oy", es: "6 meses", fr: "6 mois", hy: "6 ամիս" }, price: "$33" },
    { id: "year", label: { en: "1 year", ru: "1 год", uk: "1 рік", kk: "1 жыл", uz: "1 yil", es: "1 año", fr: "1 an", hy: "1 տարի" }, price: "$66", best: true },
    // PRICING: lifetime ("Навечно") retired from the grid — shown as "on request / coming soon",
    // not purchasable. `soon` makes it a non-selectable placeholder (see plans render).
    { id: "forever", label: { en: "On request", ru: "По запросу", uk: "На запит", kk: "Сұраныс бойынша", uz: "Soʻrov boʻyicha", es: "Bajo pedido", fr: "Sur demande", hy: "Ըստ հարցման" }, price: "$66", soon: true },
  ];
  const quests = window.LUMINARA_MISSIONS.slice(0, 4);

  const [code, setCode] = useState_e("");
  const [copied, setCopied] = useState_e(false);
  const [idents, setIdents] = useState_e([]);        // LINK: identities on this account
  const [identsLoaded, setIdentsLoaded] = useState_e(false);
  const [linkBusy, setLinkBusy] = useState_e(null);  // provider currently being linked
  const [linkMsg, setLinkMsg] = useState_e(null);    // { ok, text }
  const [tonFlow, setTonFlow] = useState_e("idle"); // idle | preparing | waiting | verified | cancelled | error
  const [mergeOffer, setMergeOffer] = useState_e(null); // provider with a link conflict → offer merge
  // Demo vs honest empty state (audit P1). window.LUMINARA_DEMO defaults true for the
  // showcase build; a real beta sets it false so a new user sees a truthful blank profile.
  const DEMO = (typeof window !== "undefined" && window.LUMINARA_DEMO !== false);
  const api = (typeof window !== "undefined") ? window.LUMINARA_API : null;
  const u = (me && me.user) ? me.user : null;
  const activation = (me && me.activation) || (typeof window !== "undefined" ? window.LUMINARA_ACTIVATION : null);
  const isActivated = DEMO || !!(activation && activation.activated);
  const [refGate, setRefGate] = useState_e(null);
  const [manualTon, setManualTon] = useState_e("");
  const [manualTonBusy, setManualTonBusy] = useState_e(false);
  const [plan, setPlan] = useState_e(DEMO ? "year" : ((u && u.plan) ? u.plan : "free"));
  const points = DEMO ? 340 : (u && typeof u.points === "number" ? u.points : 0); // server-authoritative
  const lvl = window.LUMINARA_levelFromPoints(points);
  const pct = Math.round((lvl.into / lvl.span) * 100);

  // S2-BIG.4: real referral code + stats (demo keeps the showcase values).
  const [ref, setRef] = useState_e({ url: null, invited: 0, pending: 0, converted: 0, awarded: 0, pointsEarned: 0 });
  const [refApplyState, setRefApplyState] = useState_e(null);
  useEffect_e(() => {
    if (DEMO || !api) return;
    let alive = true;
    (async () => {
      try {
        const c = await api.referral.code();    // { code, url }
        const s = await api.referral.stats();   // { invited, points_earned }
        if (!alive) return;
        setRef({ url: (c && c.url) || null,
                 invited: (s && s.invited) || 0,
                 pending: (s && s.pending) || 0,
                 converted: (s && s.converted) || 0,
                 awarded: (s && s.awarded) || 0,
                 pointsEarned: (s && (s.points_earned != null ? s.points_earned : 0)) || 0 });
      } catch (e) {
        if (e && e.status === 423) setRefGate(e.body && e.body.reason);
        else console.error("Luminara: referral stats failed —", e);
      }
    })();
    return () => { alive = false; };
  }, []);

  // LINK: load identities attached to this account, plus link handlers.
  useEffect_e(() => {
    if (DEMO || !api) return;
    let alive = true;
    (async () => {
      try { const r = await api.auth.identities(); if (alive && r) setIdents(r.identities || []); }
      catch (e) { /* non-fatal */ }
      finally { if (alive) setIdentsLoaded(true); }
    })();
    return () => { alive = false; };
  }, []);
  const refreshIdents = async () => {
    try { const r = await api.auth.identities(); setIdents((r && r.identities) || []); }
    catch (e) { /* ignore */ }
    finally { setIdentsLoaded(true); }
  };
  const saveManualTon = async () => {
    if (!api || !manualTon.trim() || manualTonBusy) return;
    setManualTonBusy(true); setLinkMsg(null);
    try {
      await api.auth.tonManual(manualTon.trim());
      setManualTon("");
      await refreshIdents();
      setLinkMsg({ ok: true, text: L(ui.unverified) });
    } catch (e) {
      setLinkMsg({ ok: false, text: (e && e.error === "wallet_taken") ? L(ui.linkConflict) : L(ui.linkErr) });
    } finally { setManualTonBusy(false); }
  };
  const verifyTonOwnership = async () => {
    if (!api || linkBusy === "ton") return;
    setLinkBusy("ton"); setLinkMsg(null); setMergeOffer(null); setTonFlow("preparing");
    try {
      const tonConnectUI = getLuminaraTonConnectUI(locale);
      await tonConnectUI.connectionRestored;
      // A restored session cannot satisfy a fresh, single-use nonce. Reconnect so the
      // wallet signs the new ton_proof payload instead of reusing stale connection data.
      if (tonConnectUI.connected) await tonConnectUI.disconnect();
      tonConnectUI.setConnectRequestParameters({ state: "loading" });
      const nonceResult = await api.auth.tonNonce();
      if (!nonceResult || !nonceResult.nonce) throw new Error("ton_nonce_failed");
      tonConnectUI.setConnectRequestParameters({
        state: "ready",
        value: { tonProof: nonceResult.nonce },
      });
      setTonFlow("waiting");
      // v3 keeps connectWallet as the promise-returning wrapper around the modal. It
      // rejects when the user closes the picker, which lets us show an honest retry state.
      const wallet = await tonConnectUI.connectWallet();
      const account = wallet && wallet.account;
      const proofItem = wallet && wallet.connectItems && wallet.connectItems.tonProof;
      const proof = proofItem && proofItem.proof;
      if (!account || !account.address || !account.publicKey || !account.walletStateInit || !proof) {
        const error = new Error("ton_proof_missing"); error.proofMissing = true; throw error;
      }
      const result = await api.auth.link("ton", {
        address: account.address,
        public_key: account.publicKey,
        state_init: account.walletStateInit,
        network: account.chain,
        proof,
      });
      if (result && Array.isArray(result.identities)) {
        setIdents(result.identities);
        setIdentsLoaded(true);
      }
      else await refreshIdents();
      if (result && result.activation) {
        window.LUMINARA_ACTIVATION = result.activation;
        window.dispatchEvent(new CustomEvent("lum:activation-changed", { detail: result.activation }));
      }
      setTonFlow("verified");
      setLinkMsg({ ok: true, text: L(ui.tonVerified) });
      if (onProfileChanged) await onProfileChanged();
    } catch (e) {
      const message = String((e && e.message) || "").toLowerCase();
      const cancelled = /abort|cancel|closed|reject/.test(message);
      if (e && e.proofMissing) {
        setTonFlow("error"); setLinkMsg({ ok: false, text: L(ui.tonProofErr) });
      } else if (e && e.status === 409) {
        // A4: TON identity belongs to another account — offer merge flow
        setTonFlow("error");
        setMergeOffer("ton");
        setLinkMsg({ ok: false, text: L(ui.linkConflict) });
      } else if (cancelled) {
        setTonFlow("cancelled"); setLinkMsg({ ok: false, text: L(ui.tonCancelled) });
      } else {
        setTonFlow("error"); setLinkMsg({ ok: false, text: L(ui.tonSdkErr) });
      }
    } finally {
      setLinkBusy(null);
    }
  };
  const acquireProof = async (provider) => {
    if (provider === "wallet") {
      const eth = (typeof window !== "undefined") ? window.ethereum : null;
      if (!eth) { const e = new Error("no_wallet"); e.noWallet = true; throw e; }
      const accounts = await eth.request({ method: "eth_requestAccounts" });
      const address = accounts && accounts[0];
      if (!address) throw new Error("no_account");
      const { nonce, message } = await api.auth.walletNonce();   // fresh, single-use
      const signature = await eth.request({ method: "personal_sign", params: [message, address] });
      return { address, signature, nonce };
    }
    const initData = (window.Telegram && window.Telegram.WebApp && window.Telegram.WebApp.initData) || "";
    if (!initData) { const e = new Error("tg_only"); e.tgOnly = true; throw e; }
    return { initData };
  };
  const linkProvider = async (provider) => {
    if (!api) return;
    setLinkBusy(provider); setLinkMsg(null); setMergeOffer(null);
    try {
      const proof = await acquireProof(provider);
      const linked = await api.auth.link(provider, proof);
      if (linked && Array.isArray(linked.identities)) {
        setIdents(linked.identities);
        setIdentsLoaded(true);
      } else {
        await refreshIdents();
      }
      setLinkMsg({ ok: true, text: L(ui.linkOk) });
      if (onProfileChanged) onProfileChanged();
    } catch (e) {
      if (e && e.noWallet) setLinkMsg({ ok: false, text: L(ui.noWallet) });
      else if (e && e.tgOnly) setLinkMsg({ ok: false, text: L(ui.tgOnly) });
      else if (e && e.status === 409) setMergeOffer(provider);   // identity on another account → offer merge
      else setLinkMsg({ ok: false, text: L(ui.linkErr) });
    } finally { setLinkBusy(null); }
  };
  const doMerge = async (provider) => {
    if (!api) return;
    setLinkBusy(provider); setLinkMsg(null);
    try {
      const proof = await acquireProof(provider);   // fresh proof (new nonce for wallet)
      const merged = await api.auth.merge(provider, proof);
      setMergeOffer(null);
      if (merged && Array.isArray(merged.identities)) {
        setIdents(merged.identities);
        setIdentsLoaded(true);
      } else {
        await refreshIdents();
      }
      setLinkMsg({ ok: true, text: L(ui.mergeOk) });
      if (onProfileChanged) onProfileChanged();
    } catch (e) {
      if (e && e.noWallet) setLinkMsg({ ok: false, text: L(ui.noWallet) });
      else if (e && e.tgOnly) setLinkMsg({ ok: false, text: L(ui.tgOnly) });
      else if (e && e.body && e.body.reason === "elevated_requires_review") setLinkMsg({ ok: false, text: L(ui.mergeReview) });
      else setLinkMsg({ ok: false, text: L(ui.mergeErr) });
    } finally { setLinkBusy(null); }
  };
  const inviteShort = DEMO ? "luminara.app/i/nova-7f3a" : (ref.url ? ref.url.replace(/^https?:\/\//, "") : "—");
  const inviteFull = DEMO ? "https://luminara.app/i/nova-7f3a" : (ref.url || "");
  const invitedCount = DEMO ? 3 : ref.invited;
  const refPoints = DEMO ? invitedCount * 50 : ref.pointsEarned;
  const verifiedTonIdentity = idents.find(x => x.provider === "ton" && x.verified);
  const manualTonIdentity = idents.find(x => x.provider === "ton" && !x.verified);
  const verifiedEvmIdentity = idents.find(x => x.provider === "wallet" && x.verified !== false);
  // identities is authoritative. /me intentionally does not expose a wallet field, so
  // deriving this badge from me.user.wallet left verified TON users in an empty state.
  const profileWallet = DEMO
    ? { label: "0x7f…3a9c", status: ui.wallet, detail: "MetaMask · Web3" }
    : verifiedTonIdentity
      ? { label: verifiedTonIdentity.label, status: ui.tonVerified, detail: "TON Connect" }
      : verifiedEvmIdentity
        ? { label: verifiedEvmIdentity.label, status: ui.wallet, detail: "EVM wallet" }
        : manualTonIdentity
          ? { label: manualTonIdentity.label, status: ui.tonSaved, detail: "TON" }
          : null;

  const copy = async () => {
    if (!inviteFull) return;
    try {
      await navigator.clipboard.writeText(inviteFull);
      setCopied(true); setTimeout(() => setCopied(false), 1600);
    } catch (e) {
      // fallback: select-prompt so the user can copy manually
      try { window.prompt(L(ui.yourLink), inviteFull); } catch (e2) {}
    }
  };
  const applyCode = async () => {
    if (!code.trim() || DEMO || !api) return;
    setRefApplyState(null);
    try {
      const result = await api.referral.apply(code.trim(), { source: "manual" });
      const key = result && result.ok ? (result.status || "pending") : ((result && result.reason) || "invalid_code");
      setRefApplyState({ ok: !!(result && result.ok), key });
      if (result && result.ok) {
        setCode("");
      }
    } catch (e) {
      console.error("Luminara: referral apply failed —", e);
      setRefApplyState({ ok: false, key: "invalid_code" });
    }
  };

  // S2-BIG.7 (a): editable display name → api.profile.update; refresh /me on save.
  const [name, setName] = useState_e((u && u.display_name) || "");
  const [nameState, setNameState] = useState_e(""); // "" | "saving" | "saved" | "err"
  const normalizeBirthday = (value) => value ? String(value).slice(0, 10) : "";
  const [birthday, setBirthday] = useState_e(normalizeBirthday(u && u.birthday));
  const [birthdayState, setBirthdayState] = useState_e("");
  useEffect_e(() => { setBirthday(normalizeBirthday(u && u.birthday)); }, [u && u.birthday]);
  // NEW-AVATAR: avatar shows the uploaded image, falling back to the name initial.
  // Picking a file previews it immediately (local object URL). Persisting to the
  // server needs the media-storage backend (avatar_url column + object store) —
  // see AVATAR-BE. Until then the preview is session-local.
  const [avatarUrl, setAvatarUrl] = useState_e((u && u.avatar_url) || null);
  const onPickAvatar = (e) => {
    const file = e.target.files && e.target.files[0];
    if (!file) return;
    const url = URL.createObjectURL(file);
    setAvatarUrl(url);
    if (api && api.profile && api.profile.uploadAvatar) {
      api.profile.uploadAvatar(file).then(r => { if (r && r.avatar_url) setAvatarUrl(r.avatar_url); }).catch(() => {});
    }
  };
  const saveName = async () => {
    if (DEMO || !api || !name.trim()) return;
    setNameState("saving");
    try {
      await api.profile.update({ display_name: name.trim() });
      setNameState("saved");
      if (onProfileChanged) onProfileChanged();          // header/avatar reflect the new name
      setTimeout(() => setNameState(""), 1600);
    } catch (e) { setNameState("err"); }
  };
  const nameUnchanged = !!(u && name.trim() === (u.display_name || ""));
  const birthdayUnchanged = birthday === normalizeBirthday(u && u.birthday);
  const saveBirthday = async () => {
    if (DEMO || !api || !isActivated || birthdayState === "saving") return;
    setBirthdayState("saving");
    try {
      await api.profile.update({ birthday: birthday || null });
      setBirthdayState("saved");
      if (onProfileChanged) await onProfileChanged();
      setTimeout(() => setBirthdayState(""), 1600);
    } catch (e) { setBirthdayState("err"); }
  };

  // S2-BIG.7 (b): data-storage consent — show expiry, extend +1 year.
  const cx = {
    h:      { en: "Data storage", ru: "Хранение данных", uk: "Зберігання даних", kk: "Деректерді сақтау", uz: "Maʼlumotlarni saqlash", es: "Almacenamiento de datos", fr: "Stockage des données", hy: "Տվյալների պահպանում" },
    note:   { en: "Your data is kept for a limited period. Extend it anytime.", ru: "Твои данные хранятся ограниченное время — продли в любой момент.", uk: "Твої дані зберігаються обмежений час — продовж будь-коли.", kk: "Деректерің шектеулі уақыт сақталады — кез келген уақытта ұзарт.", uz: "Maʼlumotlaring cheklangan muddat saqlanadi — istalgan vaqtda uzaytir.", es: "Tus datos se guardan por un período limitado. Amplíalo cuando quieras.", fr: "Tes données sont conservées pour une durée limitée. Prolonge quand tu veux.", hy: "Քո տվյալները պահվում են սահմանափակ ժամանակով։ Երկարացրու ցանկացած պահի։" },
    until:  { en: "Stored until", ru: "Хранится до", uk: "Зберігається до", kk: "Сақталу мерзімі", uz: "Saqlanadi", es: "Almacenado hasta", fr: "Conservé jusqu'au", hy: "Պահվում է մինչև" },
    extend: { en: "Extend +1 year", ru: "Продлить +1 год", uk: "Продовжити +1 рік", kk: "+1 жылға ұзарту", uz: "+1 yilga uzaytirish", es: "Ampliar +1 año", fr: "Prolonger +1 an", hy: "Երկարացնել +1 տարի" },
    delAccount: { en: "Delete account", ru: "Удалить аккаунт", uk: "Видалити акаунт", kk: "Аккаунтты жою", uz: "Hisobni oʻchirish", es: "Eliminar cuenta", fr: "Supprimer le compte", hy: "Ջնջել հաշիվը" },
    delWarn:    { en: "This permanently erases your account and all data. It can't be undone.", ru: "Это безвозвратно удалит аккаунт и все данные. Отменить нельзя.", uk: "Це назавжди видалить акаунт і всі дані. Скасувати не можна.", kk: "Бұл аккаунт пен барлық деректі қайтарымсыз жояды. Болдырмау мүмкін емес.", uz: "Bu hisobingiz va barcha maʼlumotlarni butunlay oʻchiradi. Bekor qilib boʻlmaydi.", es: "Esto borra permanentemente tu cuenta y todos los datos. No se puede deshacer.", fr: "Cela efface définitivement ton compte et toutes les données. Irréversible.", hy: "Սա ընդմիշտ ջնջում է քո հաշիվն ու բոլոր տվյալները։ Հնարավոր չէ հետարկել։" },
    delConfirm: { en: "Yes, delete everything", ru: "Да, удалить всё", uk: "Так, видалити все", kk: "Иә, бәрін жою", uz: "Ha, hammasini oʻchirish", es: "Sí, eliminar todo", fr: "Oui, tout supprimer", hy: "Այո, ջնջել ամեն ինչ" },
    delCancel:  { en: "Cancel", ru: "Отмена", uk: "Скасувати", kk: "Бас тарту", uz: "Bekor qilish", es: "Cancelar", fr: "Annuler", hy: "Չեղարկել" },
    delErr:     { en: "Couldn't delete. Try again.", ru: "Не удалось удалить. Попробуй ещё раз.", uk: "Не вдалося видалити. Спробуй ще раз.", kk: "Жою сәтсіз. Қайта көр.", uz: "Oʻchirib boʻlmadi. Yana urinib koʻr.", es: "No se pudo eliminar. Inténtalo de nuevo.", fr: "Impossible de supprimer. Réessaie.", hy: "Չհաջողվեց ջնջել։ Փորձիր նորից։" },
  };
  const fmtDate = (s) => {
    if (!s) return "—";
    try { const d = new Date(s); return isNaN(d.getTime()) ? String(s).slice(0, 10) : d.toLocaleDateString(locale === "en" ? "en-GB" : locale, { day: "2-digit", month: "short", year: "numeric" }); }
    catch (e) { return String(s).slice(0, 10); }
  };
  const [consent, setConsent] = useState_e(null); // { expires_at } | null
  const [extending, setExtending] = useState_e(false);
  const [delState, setDelState] = useState_e("idle");   // idle | confirm | deleting
  const [delErr, setDelErr] = useState_e(false);
  const [switchOpen, setSwitchOpen] = useState_e(false); // B1: switch-account explanation toggle
  // ACCOUNT-JOURNAL: real user notes live in the API. Atlas bookmarks are a separate,
  // local-only feature and must never be presented as someone’s written insight.
  const [accountInsights, setAccountInsights] = useState_e([]);
  const [accountStats, setAccountStats] = useState_e(null);
  const [insightsLoading, setInsightsLoading] = useState_e(!DEMO);
  const [insightsFailed, setInsightsFailed] = useState_e(false);
  useEffect_e(() => {
    if (DEMO || !api) { setInsightsLoading(false); return; }
    let alive = true;
    (async () => {
      setInsightsLoading(true); setInsightsFailed(false);
      const [mineResult, statsResult] = await Promise.allSettled([
        api.insights.mine(),
        api.stats.get(),
      ]);
      if (!alive) return;
      if (mineResult.status === "fulfilled" && Array.isArray(mineResult.value && mineResult.value.insights)) {
        setAccountInsights(mineResult.value.insights);
      } else {
        setInsightsFailed(true);
      }
      if (statsResult.status === "fulfilled") setAccountStats(statsResult.value || null);
      setInsightsLoading(false);
    })();
    return () => { alive = false; };
  }, [DEMO, api, u && u.id]);
  const activeSeconds = Number(accountStats && accountStats.active_seconds) || 0;
  const insightDate = (value) => {
    if (!value) return "";
    try { return new Date(value).toLocaleDateString(locale === "en" ? "en-GB" : locale, { day: "2-digit", month: "short", year: "numeric" }); }
    catch (e) { return ""; }
  };
  useEffect_e(() => {
    if (DEMO || !api) return;
    let alive = true;
    (async () => {
      try { const c = await api.consent.get(); if (alive) setConsent((c && c.data_storage) || null); }
      catch (e) { /* non-fatal */ }
    })();
    return () => { alive = false; };
  }, []);
  const extendConsent = async () => {
    if (DEMO || !api || extending) return;
    setExtending(true);
    try { const r = await api.consent.extend(); setConsent(prev => ({ ...(prev || {}), expires_at: (r && r.expires_at) || (prev && prev.expires_at) })); }
    catch (e) { /* non-fatal */ }
    finally { setExtending(false); }
  };
  // MERGE+DELME (M1): GDPR account erasure. On success the account is gone, so drop the
  // local token and return to the login screen.
  const doDelete = async () => {
    if (DEMO || !api || delState === "deleting") return;
    setDelState("deleting"); setDelErr(false);
    try {
      await api.profile.deleteAccount();        // DELETE /me → cascade erase
      try { await api.auth.logout(); } catch (e) { /* navigate regardless */ }
      window.location.href = "./Luminara_Auth.html";
    } catch (e) {
      setDelErr(true); setDelState("confirm");
    }
  };

  // S2-BIG.7 (c): logout — clear the refresh cookie, then back to the login screen.
  const doLogout = async () => {
    if (DEMO || !api) return;
    try { await api.auth.logout(); } catch (e) { /* navigate regardless */ }
    window.location.href = "./Luminara_Auth.html";
  };

  return (
    <div className="section-pad fade-in">
      <div className="section-head">
        <div className="kicker">{L(ui.title)} · 05 / 05</div>
        <h2>{L(ui.title)}</h2>
        <p>{L(ui.lede)}</p>
      </div>

      <div className="acc-grid">
        {/* B1: prominent logout / switch at the top of profile */}
        {!DEMO && (
          <div className="acc-auth-actions">
            <button className="acc-btn" onClick={doLogout}>{L(ui.logout)}</button>
            <button className="acc-btn acc-btn-switch" onClick={() => setSwitchOpen(o => !o)}>{L(ui.switchAcct)}</button>
            {switchOpen && (
              <div className="acc-switch-note">
                <p>{L(ui.switchNote)}</p>
                <button className="acc-btn primary" style={{ marginTop: 8 }} onClick={doLogout}>
                  {L(ui.switchConfirm)}
                </button>
              </div>
            )}
          </div>
        )}
        {/* Profile */}
        <div className="acc-card">
          <div className="acc-h">{L(ui.profile)}</div>
          {!isActivated && (
            <div className="acc-activation" aria-label={L(ui.activationTitle)}>
              <div className="acc-activation-head">{L(ui.activationTitle)}</div>
              <div className="acc-activation-list">
                <div className={"acc-activation-step " + ((activation && activation.first_scene) ? "done" : "todo")}>
                  <input type="checkbox" checked={!!(activation && activation.first_scene)} readOnly />
                  <span>{L(ui.activationScene)}</span>
                  {!(activation && activation.first_scene) && (
                    <button type="button" className="acc-step-action" onClick={() => { window.location.hash = "#/scene?s=0&c=preinternet"; }}>
                      {L(ui.activationOpenScene)}
                    </button>
                  )}
                </div>
                <div className={"acc-activation-step " + ((activation && activation.quiz_correct) ? "done" : "todo")}>
                  <input type="checkbox" checked={!!(activation && activation.quiz_correct)} readOnly />
                  <span>{L(ui.activationQuiz)}</span>
                  {!(activation && activation.quiz_correct) && (
                    <button type="button" className="acc-step-action" onClick={() => { window.location.hash = "#/quizzes"; }}>
                      {L(ui.activationOpenQuiz)}
                    </button>
                  )}
                </div>
                <div className={"acc-activation-step " + ((activation && activation.ton_verified) ? "done" : "todo")}>
                  <input type="checkbox" checked={!!(activation && activation.ton_verified)} readOnly />
                  <span>{L(ui.activationTon)}</span>
                </div>
              </div>
              {!(activation && activation.ton_verified) && (
                <div className="acc-ton-guide">
                  <div className="acc-activation-hint">{L(ui.activationTonHint)}</div>
                  <div className="acc-ton-next">{L(ui.tonConnectAny)}</div>
                  <button type="button" className="acc-btn primary acc-ton-connect"
                          disabled={linkBusy === "ton"} onClick={verifyTonOwnership}>
                    {linkBusy === "ton" ? L(ui.tonWaiting) : L(ui.tonConnectBtn)}
                  </button>
                  <div className="acc-ton-safety">{L(ui.tonSeedWarning)}</div>
                </div>
              )}
            </div>
          )}
          <div className="acc-id">
            <label className="acc-avatar-wrap" title={L(ui.avatarHint)}>
              {avatarUrl
                ? <img className="acc-avatar-img" src={avatarUrl} alt="" />
                : <div className="acc-avatar">{DEMO ? "N" : ((u && u.display_name) ? u.display_name.trim().charAt(0).toUpperCase() : "?")}</div>}
              <span className="acc-avatar-edit" aria-hidden="true">
                <svg viewBox="0 0 24 24" width="13" height="13" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><path d="M12 20h9"/><path d="M16.5 3.5a2.1 2.1 0 0 1 3 3L7 19l-4 1 1-4z"/></svg>
              </span>
              {!DEMO && <input type="file" accept="image/*" className="acc-avatar-input" disabled={!isActivated} onChange={onPickAvatar} />}
            </label>
            <div>
              <div className="acc-name-row">
                <span className="acc-wallet-ic">
                  <svg viewBox="0 0 24 24" width="14" height="14" fill="none"><rect x="3" y="6" width="18" height="13" rx="2.5" stroke="currentColor" strokeWidth="1.6"/><circle cx="16.5" cy="13" r="1.3" fill="currentColor"/></svg>
                </span>
                {(() => {
                  return profileWallet ? (
                    <React.Fragment>
                      <span>{profileWallet.label}</span>
                      <span className="acc-badge-live">{L(profileWallet.status)}</span>
                    </React.Fragment>
                  ) : !identsLoaded ? (
                    <span className="acc-sub">…</span>
                  ) : (
                    <span className="acc-sub">{L(ui.notConnected)}</span>
                  );
                })()}
              </div>
              <div className="acc-sub">{profileWallet ? profileWallet.detail : ""}</div>
            </div>
          </div>
          <label className="acc-label">{L(ui.name)}</label>
          <div className="acc-row">
            {/* NEW-NICK-FALLBACK: name comes from the Telegram nick; if absent the field is
                empty with a neutral placeholder (no fabricated value). */}
            <input className="acc-input acc-flex" placeholder={L(ui.namePh)}
                   value={DEMO ? "Nova" : name}
                   readOnly={DEMO || !isActivated}
                   onChange={DEMO ? undefined : (e => setName(e.target.value))} />
            {!DEMO && (
              <button className="acc-btn primary"
                      disabled={!isActivated || !name.trim() || nameState === "saving" || nameUnchanged}
                      onClick={saveName}>
                {nameState === "saved" ? L(ui.savedShort) : nameState === "saving" ? "…" : L(ui.saveShort)}
              </button>
            )}
          </div>
          <label className="acc-label">{L(ui.bday)}</label>
          <div className="acc-row">
            <input className="acc-input acc-flex" type="date" min="1900-01-01"
                   max={new Date().toISOString().slice(0, 10)} value={birthday}
                   disabled={DEMO || !isActivated}
                   onChange={(e) => { setBirthday(e.target.value); setBirthdayState(""); }} />
            {!DEMO && birthday && <button type="button" className="acc-btn" disabled={!isActivated} onClick={() => { setBirthday(""); setBirthdayState(""); }}>{L(ui.clear)}</button>}
            {!DEMO && <button type="button" className="acc-btn primary"
                             disabled={!isActivated || birthdayUnchanged || birthdayState === "saving"}
                             onClick={saveBirthday}>
              {birthdayState === "saved" ? L(ui.savedShort) : birthdayState === "saving" ? "…" : L(ui.saveShort)}
            </button>}
          </div>
          <div className="acc-sub" style={{ marginTop: 6 }}>{L(ui.bdayNote)}</div>
        </div>

        {/* Linked logins (LINK / L2) */}
        <div className="acc-card">
          <div className="acc-h">{L(ui.linked)}</div>
          {DEMO ? (
            <div>
              <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", padding: "8px 0" }}>
                <span style={{ textTransform: "capitalize" }}>Telegram</span>
                <span className="acc-badge-live">@nova</span>
              </div>
              <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", padding: "8px 0", borderTop: "1px solid var(--hairline, rgba(255,255,255,.08))" }}>
                <span style={{ textTransform: "capitalize" }}>Wallet</span>
                <span className="acc-badge-live">0x7f…3a9c</span>
              </div>
            </div>
          ) : (
            <div>
              {["telegram", "wallet", "ton"].map((p, i) => {
                const it = idents.find(x => x.provider === p);
                return (
                  <div key={p}
                       style={{ display: "flex", justifyContent: "space-between", alignItems: "center", padding: "8px 0", borderTop: i ? "1px solid var(--hairline, rgba(255,255,255,.08))" : "none" }}>
                    <span style={{ textTransform: "capitalize" }}>{p}</span>
                    {p === "ton" ? (
                      <div className="acc-ton-row-action">
                        <span className={it && it.verified ? "acc-badge-live" : "acc-sub"}>
                          {it ? (it.label || L(ui.linkOk)) : L(ui.notConnected)}
                          {it && !it.verified ? ` · ${L(ui.unverified)}` : ""}
                        </span>
                        {!(it && it.verified) && (
                          <button type="button" className="acc-btn primary" disabled={linkBusy === "ton"}
                                  onClick={verifyTonOwnership}>
                            {linkBusy === "ton" ? "…" : L(ui.tonConnectBtn)}
                          </button>
                        )}
                      </div>
                    ) : it ? (
                      <span className="acc-badge-live">{it.label || L(ui.linkOk)}</span>
                    ) : (
                      <button className="acc-btn" disabled={linkBusy === p} onClick={() => linkProvider(p)}>
                        {linkBusy === p ? "…" : L(ui.linkBtn)}
                      </button>
                    )}
                  </div>
                );
              })}
              {!idents.some(x => x.provider === "ton" && x.verified) && (
                <div style={{ marginTop: 12 }}>
                  <label className="acc-label">{L(ui.manualTon)}</label>
                  <div className="acc-row">
                    <input className="acc-input acc-flex" value={manualTon}
                           placeholder={L(ui.manualTonPh)} onChange={e => setManualTon(e.target.value)} />
                    <button className="acc-btn" disabled={!manualTon.trim() || manualTonBusy} onClick={saveManualTon}>
                      {manualTonBusy ? "…" : L(ui.manualTonSave)}
                    </button>
                  </div>
                  <div className="acc-sub" style={{ marginTop: 6 }}>{L(ui.manualTonNote)}</div>
                </div>
              )}
              {linkMsg && (
                <div className="acc-sub" style={{ marginTop: 8, color: linkMsg.ok ? "var(--c-forest, #3fb950)" : "var(--c-crimson, #e5484d)" }}>
                  {linkMsg.text}
                </div>
              )}
              {mergeOffer && (
                <div style={{ marginTop: 12, padding: "10px 12px", borderRadius: 10, background: "rgba(229,72,77,.08)", border: "1px solid var(--c-crimson, #e5484d)" }}>
                  <p className="acc-sub" style={{ marginBottom: 10 }}>{L(ui.mergeOffer)}</p>
                  <div className="acc-row">
                    <button className="acc-btn" disabled={linkBusy === mergeOffer} onClick={() => setMergeOffer(null)}>{L(ui.mergeCancel)}</button>
                    <button className="acc-btn primary" disabled={linkBusy === mergeOffer} onClick={() => doMerge(mergeOffer)}>
                      {linkBusy === mergeOffer ? "…" : L(ui.mergeBtn)}
                    </button>
                  </div>
                </div>
              )}
            </div>
          )}
        </div>

        {/* ACCOUNT-JOURNAL: the source of truth for personal notes and attention metrics. */}
        <div className="acc-card acc-wide">
          <div className="acc-h acc-h-row">
            <span>{L(ui.insights)}</span>
            <button className="acc-missions-all" onClick={onOpenInsights}>{L(ui.allInsights)} →</button>
          </div>
          <div className="journal-metrics account-journal-metrics">
            <div className="jm">
              <div className="jm-k mono">{L(ui.insights)}</div>
              <div className="jm-v">{insightsLoading ? "—" : accountInsights.length}</div>
              <div className="jm-t mono">{L(ui.insightCount)}</div>
            </div>
            <div className="jm">
              <div className="jm-k mono">{L(ui.streak)}</div>
              <div className="jm-v">{accountStats ? accountStats.streak : "—"} <span className="jm-u">{L(ui.days)}</span></div>
              <div className="jm-t mono">{L(ui.streak)}</div>
            </div>
            <div className="jm jm-accent">
              <div className="jm-k mono">{L(ui.activeHours)}</div>
              <div className="jm-v">
                {accountStats ? Math.floor(activeSeconds / 3600) : "—"}<span className="jm-u">h</span>{" "}
                {accountStats ? Math.floor((activeSeconds % 3600) / 60) : "—"}<span className="jm-u">m</span>
              </div>
              <div className="jm-t mono">{L(ui.activeNote)}</div>
            </div>
          </div>
          {insightsFailed ? (
            <div className="acc-sub account-insights-empty">{L(ui.insightsUnavailable)}</div>
          ) : !insightsLoading && accountInsights.length === 0 ? (
            <div className="acc-sub account-insights-empty">{L(ui.noInsights)}</div>
          ) : (
            <div className="account-insights-list">
              {accountInsights.map((item) => (
                <article className="account-insight" key={item.id}>
                  <div className="account-insight-meta mono">
                    <span>{item.topic || "—"}</span>
                    <span>{insightDate(item.created_at)}</span>
                  </div>
                  <p>{item.body}</p>
                </article>
              ))}
            </div>
          )}
        </div>

        {/* Honest learning metrics: authored chapter count + server quiz attempts. */}
        <div className="acc-card">
          <div className="acc-h">{L(ui.learning)}</div>
          <div className="acc-level">
            <span className="acc-level-name">{(learning && learning.percent) || 0}%</span>
          </div>
          <div className="acc-lp-bar"><i style={{ width: ((learning && learning.percent) || 0) + "%" }} /></div>
          <div className="acc-sub" style={{ marginTop: 8 }}>
            {(learning && learning.completedScenes) || 0} / {(learning && learning.totalScenes) || 0} {L(ui.chaptersDone)}
          </div>
          <div className="acc-learning-quiz">
            <span>{L(ui.quizAverage)}</span>
            <b>{learning && learning.quiz && learning.quiz.scorePct != null ? learning.quiz.scorePct + "%" : "—"}</b>
          </div>
          {!(learning && learning.quiz && learning.quiz.attempted) && <div className="acc-sub">{L(ui.noQuizYet)}</div>}
        </div>

        {/* Rating / level (TICKET-043) */}
        <div className="acc-card">
          <div className="acc-h">{L(ui.status)}</div>
          <div className="acc-status">{L(ui.free)}</div>
          <div className="acc-level">
            <span className="acc-level-name">{L(lvl.tier)}</span>
            <span className="acc-level-sub">{"I".repeat(lvl.sub)}</span>
            {lvl.cycle > 0 && <span className="acc-level-cycle">★{lvl.cycle}</span>}
          </div>
          <div className="acc-lp-bar"><i style={{ width: pct + "%" }} /></div>
          <div className="acc-sub" style={{ marginTop: 8 }}>
            {points} {L(ui.points)} · {lvl.nextAt - points} {L(ui.toNext)}
          </div>
        </div>

        {/* Referral */}
        <div className="acc-card acc-wide">
          <div className="acc-h">{L(ui.referral)}</div>
          {refGate && <div className="acc-lock-note">🔒 {L(ui.walletNeeded)}</div>}
          <label className="acc-label">{L(ui.yourLink)}</label>
          <div className="acc-row">
            <input className="acc-input acc-flex" readOnly value={inviteShort} />
            <button className="acc-btn" disabled={!inviteFull} onClick={copy}>{copied ? L(ui.copied) : L(ui.copy)}</button>
          </div>
          <div className="acc-ref-stats">
            <div><b>{invitedCount}</b> {L(ui.invited)}</div>
            <div><b>{DEMO ? 1 : ref.pending}</b> {L(ui.pending)}</div>
            <div><b>{DEMO ? 2 : ref.converted}</b> {L(ui.converted)}</div>
            <div><b>{DEMO ? 1 : ref.awarded}</b> {L(ui.awarded)}</div>
            <div><b>+{refPoints}</b> {L(ui.earned)}</div>
          </div>
          <label className="acc-label" style={{ marginTop: 14 }}>{L(ui.haveCode)}</label>
          <div className="acc-row">
            <input className="acc-input acc-flex" placeholder={L(ui.codePh)} value={code} onChange={e => setCode(e.target.value)} />
            <button className="acc-btn primary" disabled={!code.trim()} onClick={applyCode}>{L(ui.apply)}</button>
          </div>
          {refApplyState && (
            <div className={"acc-ref-message " + (refApplyState.ok ? "ok" : "err")}>
              {(refMessages[locale] || refMessages.en)[refApplyState.key] || (refMessages[locale] || refMessages.en).invalid_code}
            </div>
          )}
        </div>

        {/* Data storage / consent */}
        <div className="acc-card acc-wide">
          <div className="acc-h">{L(cx.h)}</div>
          <p className="acc-sub" style={{ marginBottom: 12 }}>{L(cx.note)}</p>
          <div className="acc-row" style={{ alignItems: "flex-end" }}>
            <div className="acc-flex">
              <div className="acc-label" style={{ marginBottom: 4 }}>{L(cx.until)}</div>
              <div style={{ fontFamily: "var(--f-mono)", fontSize: 14 }}>
                {DEMO ? "31 Dec 2026" : (consent && consent.expires_at ? fmtDate(consent.expires_at) : "—")}
              </div>
            </div>
            <button className="acc-btn primary" disabled={DEMO || extending} onClick={extendConsent}>
              {extending ? "…" : L(cx.extend)}
            </button>
          </div>
          {/* Danger zone: GDPR delete (MERGE+DELME / M1) */}
          <div style={{ marginTop: 18, paddingTop: 14, borderTop: "1px solid var(--hairline, rgba(255,255,255,.08))" }}>
            {delState === "idle" ? (
              <button className="acc-btn" disabled={DEMO}
                      style={{ color: "var(--c-crimson, #e5484d)", borderColor: "var(--c-crimson, #e5484d)" }}
                      onClick={() => setDelState("confirm")}>
                {L(cx.delAccount)}
              </button>
            ) : (
              <div>
                <p className="acc-sub" style={{ marginBottom: 10, color: "var(--c-crimson, #e5484d)" }}>{L(cx.delWarn)}</p>
                <div className="acc-row">
                  <button className="acc-btn" disabled={delState === "deleting"} onClick={() => setDelState("idle")}>{L(cx.delCancel)}</button>
                  <button className="acc-btn" disabled={delState === "deleting"}
                          style={{ background: "var(--c-crimson, #e5484d)", color: "#fff", borderColor: "var(--c-crimson, #e5484d)" }}
                          onClick={doDelete}>
                    {delState === "deleting" ? "…" : L(cx.delConfirm)}
                  </button>
                </div>
                {delErr && <div className="acc-sub" style={{ marginTop: 8, color: "var(--c-crimson, #e5484d)" }}>{L(cx.delErr)}</div>}
              </div>
            )}
          </div>
        </div>

        {/* Subscription */}
        <div className="acc-card acc-wide">
          <div className="acc-h">{L(ui.sub)}</div>
          <div className="acc-sub" style={{ marginBottom: 12 }}>{L(ui.subNote)}</div>
          <div className="acc-plans">
            {plans.map(p => (
              <button key={p.id}
                      className={"acc-plan" + (plan === p.id ? " active" : "") + (p.best ? " best" : "") + (p.soon ? " soon" : "")}
                      disabled={!!p.soon}
                      onClick={() => { if (!p.soon) setPlan(p.id); }}>
                {p.best && <span className="acc-plan-best">★</span>}
                <span className="acc-plan-label">{L(p.label)}</span>
                <span className="acc-plan-price">{p.soon ? L(ui.soon) : p.price}</span>
              </button>
            ))}
          </div>
        </div>

        {/* Missions (preview — first few, full list on its own page) */}
        <div className="acc-card acc-wide">
          <div className="acc-h acc-h-row">
            <span>{L(ui.missions)}</span>
            <button className="acc-missions-all" onClick={onOpenMissions}>{L(ui.allMissions)} →</button>
          </div>
          <div className="acc-sub" style={{ marginBottom: 12 }}>{L(ui.missionNote)}</div>
          <div className="acc-quests">
            {quests.map(q => (
              <div key={q.id} className={"acc-quest" + (DEMO && q.done ? " done" : "")}>
                <span className="acc-quest-check">{DEMO && q.done ? "✓" : ""}</span>
                <span className="acc-quest-label">{L(q.label)}</span>
                <span className="acc-quest-lp">+{q.lp}</span>
              </div>
            ))}
          </div>
        </div>

        {/* My Universe (personal concept map) — B2 */}
        {window.MyUniverseCard ? <window.MyUniverseCard locale={locale} onOpen={onOpenUniverse} /> : null}

        {/* Certificates / SBT placeholder — TICKET-046 */}
        <div className="acc-card acc-wide">
          <div className="acc-h acc-h-row">
            <span>{L(ui.sbt)}</span>
            <span className="acc-soon">{L(ui.soon)}</span>
          </div>
          <div className="acc-sub">{L(ui.sbtNote)}</div>
          <div className="acc-sbt-row">
            <div className="acc-sbt-badge locked">⬡</div>
            <div className="acc-sbt-badge locked">⬡</div>
            <div className="acc-sbt-badge locked">⬡</div>
          </div>
        </div>
      </div>

      {!DEMO && (
        <div style={{ textAlign: "center", marginTop: 22 }}>
          <button className="acc-btn" onClick={doLogout}>{L(ui.logout)}</button>
        </div>
      )}
    </div>
  );
}
window.Account = Account;

// ── Missions data (shared between Account preview and full Missions page) ──
window.LUMINARA_MISSIONS = [
  { id: "tg",      label: { en: "Join our Telegram channel", ru: "Вступить в наш Telegram-канал", uk: "Долучитись до Telegram-каналу", kk: "Telegram-арнаға қосылу", uz: "Telegram-kanalimizga qoʻshilish", es: "Únete a nuestro canal de Telegram", fr: "Rejoins notre canal Telegram", hy: "Միացիր մեր Telegram ալիքին" }, lp: 20, done: true,  cat: "social" },
  { id: "wallet",  label: { en: "Create a Web3 wallet", ru: "Создать Web3-кошелёк", uk: "Створити Web3-гаманець", kk: "Web3-әмиян жасау", uz: "Web3-hamyon yaratish", es: "Crea una billetera Web3", fr: "Crée un portefeuille Web3", hy: "Ստեղծիր Web3 դրամապանակ" }, lp: 50, done: false, cat: "useful" },
  { id: "invite",  label: { en: "Invite a friend", ru: "Пригласить друга", uk: "Запросити друга", kk: "Досыңды шақыру", uz: "Doʻstingni taklif qilish", es: "Invita a un amigo", fr: "Invite un ami", hy: "Հրավիրիր ընկերոջ" }, lp: 50, done: false, cat: "growth" },
  { id: "tonwallet",label:{ en: "Connect a TON wallet", ru: "Привязать TON-кошелёк", uk: "Підключити TON-гаманець", kk: "TON-әмиянды жалғау", uz: "TON-hamyonni ulash", es: "Conecta una billetera TON", fr: "Connecte un portefeuille TON", hy: "Միացրու TON դրամապանակ" }, lp: 60, done: false, cat: "useful" },
  { id: "stake",   label: { en: "Try staking on Arbitrum", ru: "Попробовать стейкинг в Arbitrum", uk: "Спробувати стейкінг в Arbitrum", kk: "Arbitrum-да стейкинг сынау", uz: "Arbitrumda staking sinab koʻrish", es: "Prueba el staking en Arbitrum", fr: "Essaie le staking sur Arbitrum", hy: "Փորձիր staking Arbitrum-ում" }, lp: 100, done: false, cat: "useful" },
  { id: "insight", label: { en: "Save 5 insights to your journal", ru: "Сохранить 5 инсайтов в журнал", uk: "Зберегти 5 інсайтів у журнал", kk: "Журналға 5 инсайт сақтау", uz: "Jurnalga 5 ta insayt saqlash", es: "Guarda 5 ideas en tu diario", fr: "Sauvegarde 5 idées dans ton journal", hy: "Պահիր 5 գաղափար քո օրագրում" }, lp: 40, done: false, cat: "learn" },
  { id: "block",   label: { en: "Complete the TON block", ru: "Пройти блок TON", uk: "Пройти блок TON", kk: "TON блогын өту", uz: "TON blokini yakunlash", es: "Completa el bloque TON", fr: "Termine le bloc TON", hy: "Ավարտիր TON բլոկը" }, lp: 120, done: false, cat: "learn" },
];

// ── Rating / level system (TICKET-043) — infinite Call-of-Duty-style growth ──
// Tiers cycle with ascending ranks; each tier has 3 sub-levels. Points feed the level.
window.LUMINARA_LEVELS = (() => {
  const tiers = [
    { en: "Curious",      ru: "Любознательный", uk: "Допитливий",   kk: "Білуге құмар",   uz: "Qiziquvchan", es: "Curioso", fr: "Curieux", hy: "Հետաքրքրասեր" },
    { en: "Inception",    ru: "Начало",         uk: "Початок",      kk: "Бастама",        uz: "Boshlanish", es: "Inicio", fr: "Commencement", hy: "Սկիզբ" },
    { en: "Professional", ru: "Профессионал",   uk: "Професіонал",  kk: "Кәсіпқой",       uz: "Professional", es: "Profesional", fr: "Professionnel", hy: "Պրոֆեսիոնալ" },
    { en: "Super Pro",    ru: "Супер-профи",    uk: "Супер-профі",  kk: "Супер-кәсіпқой", uz: "Super-pro", es: "Súper Pro", fr: "Super Pro", hy: "Սուպեր Պրո" },
    { en: "Elite",        ru: "Элита",          uk: "Еліта",        kk: "Элита",          uz: "Elita", es: "Élite", fr: "Élite", hy: "Էլիտար" },
    { en: "Super Elite",  ru: "Супер-элита",    uk: "Супер-еліта",  kk: "Супер-элита",    uz: "Super-elita", es: "Súper Élite", fr: "Super Élite", hy: "Սուպեր Էլիտար" },
    { en: "Chosen",       ru: "Избранный",      uk: "Обраний",      kk: "Таңдаулы",       uz: "Tanlangan", es: "Elegido", fr: "Élu", hy: "Ընտրյալ" },
  ];
  const STEP = 100; // points per sub-level
  return { tiers, STEP, perTier: 3 };
})();

// Resolve points → { tierName{}, sub (1..3), tierIndex, cycle, into, span, nextAt }
window.LUMINARA_levelFromPoints = (points) => {
  const { tiers, STEP, perTier } = window.LUMINARA_LEVELS;
  const lvlIndex = Math.floor(Math.max(0, points) / STEP); // 0,1,2,...
  const tierIndex = Math.floor(lvlIndex / perTier) % tiers.length;
  const cycle = Math.floor(Math.floor(lvlIndex / perTier) / tiers.length); // prestige cycles
  const sub = (lvlIndex % perTier) + 1; // 1..3
  const into = points - lvlIndex * STEP;
  return { tier: tiers[tierIndex], sub, tierIndex, cycle, into, span: STEP, nextAt: (lvlIndex + 1) * STEP, lvlIndex };
};

// ── Shared anonymous insights (TICKET-039/040/041) — demo feed, by topic ──
// Real backend later; this seeds the rotating ribbon and in-lesson insights.
window.LUMINARA_INSIGHTS_SEED = [
  { topic: "web1",     text: { en: "Web1 was read-only — we forget the internet once had no 'post' button.", ru: "Web1 был только для чтения — мы забываем, что у интернета когда-то не было кнопки «опубликовать».", uk: "Web1 був лише для читання — ми забуваємо, що в інтернету колись не було кнопки «опублікувати».", kk: "Web1 тек оқуға арналған еді — интернетте бір кездері «жариялау» түймесі болмағанын ұмытамыз.", uz: "Web1 faqat oʻqish uchun edi — internetda bir paytlar «eʼlon qilish» tugmasi boʻlmaganini unutamiz.", es: "La Web1 era solo de lectura; olvidamos que internet alguna vez no tuvo botón de 'publicar'.", fr: "Le Web1 était en lecture seule — on oublie qu'internet n'a pas toujours eu de bouton « publier ».", hy: "Web1-ը միայն կարդալու համար էր՝ մոռանում ենք, որ ինտերնետը ժամանակին «հրապարակել» կոճակ չուներ։" } },
  { topic: "web2",     text: { en: "I realised 'free' platforms charge in attention, not money.", ru: "Я понял, что «бесплатные» платформы берут плату вниманием, а не деньгами.", uk: "Я зрозумів, що «безкоштовні» платформи беруть плату увагою, а не грошима.", kk: "«Тегін» платформалар ақшамен емес, назармен төлетеді екенін түсіндім.", uz: "«Bepul» platformalar pul emas, eʼtibor evaziga toʻlov olishini angladim.", es: "Me di cuenta de que las plataformas 'gratis' cobran en atención, no en dinero.", fr: "J'ai compris que les plateformes « gratuites » se paient en attention, pas en argent.", hy: "Հասկացա, որ «անվճար» հարթակները գանձում են ուշադրությամբ, ոչ թե փողով։" } },
  { topic: "web2",     text: { en: "Data is the new oil only because we agreed to pump it for free.", ru: "Данные — новая нефть лишь потому, что мы согласились качать её бесплатно.", uk: "Дані — нова нафта лише тому, що ми погодилися качати її безкоштовно.", kk: "Деректер — жаңа мұнай, өйткені біз оны тегін айдауға келістік.", uz: "Maʼlumotlar — yangi neft, chunki biz uni bepul haydashga rozi boʻldik.", es: "Los datos son el nuevo petróleo solo porque aceptamos extraerlos gratis.", fr: "Les données sont le nouveau pétrole seulement parce qu'on accepte de les extraire gratuitement.", hy: "Տվյալները նոր նավթն են միայն այն պատճառով, որ համաձայնեցինք դրանք անվճար մղել։" } },
  { topic: "web30",    text: { en: "The feed isn't showing me the world — it's showing me a model of me.", ru: "Лента показывает не мир — она показывает модель меня самого.", uk: "Стрічка показує не світ — вона показує модель мене самого.", kk: "Лента маған әлемді емес — өзімнің моделімді көрсетеді.", uz: "Lenta menga dunyoni emas — oʻzimning modelimni koʻrsatadi.", es: "El feed no me muestra el mundo: me muestra un modelo de mí.", fr: "Le fil ne me montre pas le monde — il me montre un modèle de moi.", hy: "Հոսքը ինձ աշխարհը չի ցույց տալիս՝ ինձ ցույց է տալիս իմ մոդելը։" } },
  { topic: "web3",     text: { en: "A wallet is the first online account no company can delete.", ru: "Кошелёк — первый онлайн-аккаунт, который не может удалить ни одна компания.", uk: "Гаманець — перший онлайн-акаунт, який не може видалити жодна компанія.", kk: "Әмиян — ешбір компания жоя алмайтын алғашқы онлайн-аккаунт.", uz: "Hamyon — hech bir kompaniya oʻchira olmaydigan birinchi onlayn hisob.", es: "Una billetera es la primera cuenta online que ninguna empresa puede borrar.", fr: "Un portefeuille est le premier compte en ligne qu'aucune entreprise ne peut supprimer.", hy: "Դրամապանակն առաջին առցանց հաշիվն է, որ ոչ մի ընկերություն չի կարող ջնջել։" } },
  { topic: "economy",  text: { en: "Value moved from factories to networks while I wasn't looking.", ru: "Ценность переехала с фабрик в сети, пока я не смотрел.", uk: "Цінність переїхала з фабрик у мережі, поки я не дивився.", kk: "Құндылық мен қарамай тұрғанда фабрикалардан желілерге көшіп кетті.", uz: "Qiymat men qaramayotganimda fabrikalardan tarmoqlarga koʻchdi.", es: "El valor pasó de las fábricas a las redes sin que me diera cuenta.", fr: "La valeur est passée des usines aux réseaux sans que je m'en aperçoive.", hy: "Արժեքը գործարաններից տեղափոխվեց ցանցեր, մինչ ես չէի նկատում։" } },
  { topic: "netocracy",text: { en: "Attention is power now — and I give mine away for free all day.", ru: "Внимание теперь — это власть, а я раздаю своё бесплатно весь день.", uk: "Увага тепер — це влада, а я роздаю свою безкоштовно весь день.", kk: "Назар енді — билік, ал мен өзімдікін күні бойы тегін таратам.", uz: "Eʼtibor endi — bu kuch, men esa oʻzimnikini kun boʻyi bepul tarqataman.", es: "La atención ahora es poder, y yo regalo la mía gratis todo el día.", fr: "L'attention est désormais un pouvoir — et je donne la mienne gratuitement toute la journée.", hy: "Ուշադրությունն այժմ իշխանություն է, և ես իմը անվճար տալիս եմ ողջ օրը։" } },
  { topic: "ton",      text: { en: "Telegram quietly became an economy, not just a messenger.", ru: "Telegram тихо стал экономикой, а не просто мессенджером.", uk: "Telegram тихо став економікою, а не просто месенджером.", kk: "Telegram тыныш қана экономикаға айналды, жай мессенджер емес.", uz: "Telegram jimgina iqtisodiyotga aylandi, shunchaki messenjer emas.", es: "Telegram se convirtió silenciosamente en una economía, no solo en un mensajero.", fr: "Telegram est devenu discrètement une économie, pas seulement une messagerie.", hy: "Telegram-ը հանգիստ դարձավ տնտեսություն, ոչ միայն մեսենջեր։" } },
  { topic: "bitcoin",  text: { en: "Bitcoin's halving is the only schedule no government can edit.", ru: "Халвинг биткоина — единственное расписание, которое не может изменить ни одно правительство.", uk: "Халвінг біткоїна — єдиний розклад, який не може змінити жоден уряд.", kk: "Биткоин халвингі — ешбір үкімет өзгерте алмайтын жалғыз кесте.", uz: "Bitkoin halvingi — hech bir hukumat oʻzgartira olmaydigan yagona jadval.", es: "El halving de Bitcoin es el único calendario que ningún gobierno puede editar.", fr: "Le halving de Bitcoin est le seul calendrier qu'aucun gouvernement ne peut modifier.", hy: "Bitcoin-ի halving-ը միակ ժամանակացույցն է, որ ոչ մի կառավարություն չի կարող փոխել։" } },
];


function Missions({ t, locale, onBack, onPointsChanged }) {
  const L = (o) => o[locale] || o.en;
  const ui = {
    title:   { en: "Your missions", ru: "Твои миссии", uk: "Твої місії", kk: "Сенің миссияларың", uz: "Sening missiyalaring", es: "Tus misiones", fr: "Tes missions", hy: "Քո առաքելությունները" },
    lede:    { en: "Every completed step is real progress. New missions appear regularly.", ru: "Каждый выполненный шаг — это настоящий прогресс. Новые миссии появляются регулярно.", uk: "Кожен виконаний крок — це справжній прогрес. Нові місії з’являються регулярно.", kk: "Әр орындалған қадам — нағыз прогресс. Жаңа миссиялар үнемі пайда болады.", uz: "Har bir bajarilgan qadam — bu haqiqiy progress. Yangi missiyalar muntazam paydo boʻladi.", es: "Cada paso completado es progreso real. Aparecen nuevas misiones con regularidad.", fr: "Chaque étape terminée est un vrai progrès. De nouvelles missions apparaissent régulièrement.", hy: "Յուրաքանչյուր ավարտած քայլ իրական առաջընթաց է։ Նոր առաքելություններ հայտնվում են պարբերաբար։" },
    back:    { en: "← Account", ru: "← Кабинет", uk: "← Кабінет", kk: "← Кабинет", uz: "← Kabinet", es: "← Cuenta", fr: "← Compte", hy: "← Հաշիվ" },
    done:    { en: "Done", ru: "Сделано", uk: "Зроблено", kk: "Орындалды", uz: "Bajarildi", es: "Hecho", fr: "Terminé", hy: "Կատարված" },
    earned:  { en: "earned", ru: "получено", uk: "отримано", kk: "жиналды", uz: "yigʻildi", es: "ganados", fr: "gagnés", hy: "վաստակած" },
    pts:     { en: "pts", ru: "балл", uk: "бал", kk: "ұпай", uz: "ball", es: "pts", fr: "pts", hy: "մ" },
    notYet:  { en: "Not yet — finish this step, then check again", ru: "Пока не выполнено — заверши шаг и проверь снова", uk: "Ще не виконано — заверши крок і перевір знову", kk: "Әзірге орындалмаған — қадамды аяқтап, қайта тексер", uz: "Hali bajarilmadi — qadamni tugatib, qayta tekshir", es: "Aún no: completa este paso y vuelve a comprobar", fr: "Pas encore — termine cette étape, puis revérifie", hy: "Դեռ ոչ՝ ավարտիր այս քայլը, ապա ստուգիր նորից" },
    infoMission:{ en: "Guidance step — do it out in the world; we can't auto-verify this one yet", ru: "Это ориентир — сделай в реальном мире; автопроверки пока нет", uk: "Це орієнтир — зроби в реальному світі; автоперевірки поки немає", kk: "Бұл бағдар — нақты әлемде орында; әзірге автотексеру жоқ", uz: "Bu yoʻriqnoma — real dunyoda bajar; hozircha avto-tekshiruv yoʻq", es: "Paso guiado: hazlo en el mundo real; aún no podemos verificarlo automáticamente", fr: "Étape guidée — fais-le dans le monde réel ; on ne peut pas encore le vérifier automatiquement", hy: "Ուղղորդող քայլ՝ արա այն իրական աշխարհում, սա դեռ չենք կարող ավտոմատ ստուգել" },
    verifyErr:{ en: "Couldn't check right now — try again", ru: "Не удалось проверить — попробуй ещё раз", uk: "Не вдалося перевірити — спробуй ще раз", kk: "Тексеру мүмкін болмады — қайта көр", uz: "Tekshirib boʻlmadi — qayta urin", es: "No se pudo comprobar ahora: inténtalo de nuevo", fr: "Vérification impossible pour l'instant — réessaie", hy: "Հիմա չհաջողվեց ստուգել՝ փորձիր նորից" },
  };
  // Motivational lines (TICKET-013) — effort/progress tone, NOT flattery.
  const PRAISE = {
    en: ["You made progress today.", "You got a little better.", "That's a step forward.", "Step done — keep moving."],
    ru: ["Ты сделал прогресс сегодня.", "Ты стал сегодня чуть лучше.", "Это шаг вперёд.", "Шаг сделан — двигайся дальше."],
    uk: ["Ти зробив прогрес сьогодні.", "Ти став сьогодні трохи кращим.", "Це крок уперед.", "Крок зроблено — рухайся далі."],
    kk: ["Бүгін прогресс жасадың.", "Бүгін сәл жақсырақ болдың.", "Бұл алға қадам.", "Қадам жасалды — әрі қарай жүр."],
    uz: ["Bugun progress qilding.", "Bugun biroz yaxshilanding.", "Bu — oldinga qadam.", "Qadam bajarildi — davom et."],
  };
  const praiseList = PRAISE[locale] || PRAISE.en;

  const [state, setState] = useState_e(() => {
    const demo = (typeof window !== "undefined" && window.LUMINARA_DEMO !== false);
    const m = {}; window.LUMINARA_MISSIONS.forEach(q => { m[q.id] = demo ? !!q.done : false; }); return m;
  });
  const [flash, setFlash] = useState_e(null); // {id, text}
  const [busy, setBusy] = useState_e(null);   // id currently being verified

  // Missions-UI: the server is the source of truth (P1-1). In demo mode there is
  // no API, so we keep the old optimistic local toggle for the standalone preview.
  const api = (typeof window !== "undefined") ? window.LUMINARA_API : null;
  const DEMO = (typeof window !== "undefined" && window.LUMINARA_DEMO !== false);

  const showFlash = (id, text, ms) => {
    setFlash({ id, text });
    setTimeout(() => setFlash(f => (f && f.id === id ? null : f)), ms || 2600);
  };

  // Real mode: hydrate done-state from mission_progress on mount.
  useEffect_e(() => {
    if (DEMO || !api) return;
    let alive = true;
    (async () => {
      try {
        const r = await api.missions.list();        // { missions:[{mission_id, done}], verifiable }
        const rows = (r && r.missions) || [];
        if (!alive) return;
        setState(s => {
          const m = { ...s };
          rows.forEach(row => { if (row && row.mission_id != null) m[row.mission_id] = !!row.done; });
          return m;
        });
      } catch (e) { /* keep current state on failure */ }
    })();
    return () => { alive = false; };
  }, []);

  // Demo-only optimistic toggle (no server, preview).
  const localToggle = (q) => {
    setState(s => {
      const next = !s[q.id];
      if (next) showFlash(q.id, praiseList[Math.floor(Math.random() * praiseList.length)]);
      else setFlash(f => (f && f.id === q.id ? null : f));
      return { ...s, [q.id]: next };
    });
  };

  // Real verify: server checks the fact (identity / wallet / insight / referral) and
  // awards points idempotently; informational missions are acknowledged but never award.
  const verify = async (q) => {
    if (DEMO || !api) { localToggle(q); return; }
    if (busy || state[q.id]) return;
    setBusy(q.id);
    try {
      const r = await api.missions.verify(q.id);
      if (r && r.verified) {
        setState(s => ({ ...s, [q.id]: true }));
        showFlash(q.id, praiseList[Math.floor(Math.random() * praiseList.length)]);
        if (typeof onPointsChanged === "function") onPointsChanged(); // refresh header points from /me
      } else if (r && r.informational) {
        showFlash(q.id, L(ui.infoMission), 3600);
      } else {
        showFlash(q.id, L(ui.notYet), 3200);
      }
    } catch (e) {
      showFlash(q.id, L(ui.verifyErr), 3200);
    } finally {
      setBusy(null);
    }
  };

  const totalLp = window.LUMINARA_MISSIONS.reduce((sum, q) => sum + (state[q.id] ? q.lp : 0), 0);

  return (
    <div className="section-pad fade-in">
      <button className="eco-back" onClick={onBack}>{L(ui.back)}</button>
      <div className="section-head">
        <div className="kicker">{t.nav?.account || "Account"} · {L(ui.title)}</div>
        <h2>{L(ui.title)}</h2>
        <p>{L(ui.lede)}</p>
      </div>

      <div className="missions-summary">
        <div className="ms-v">{totalLp} <span className="ms-u">{L(ui.pts)}</span></div>
        <div className="ms-k mono">{L(ui.earned)}</div>
      </div>

      <div className="missions-list">
        {window.LUMINARA_MISSIONS.map(q => {
          const done = state[q.id];
          return (
            <div key={q.id} className={"mission" + (done ? " done" : "")}>
              <button className="mission-check" onClick={() => verify(q)}
                      disabled={busy === q.id || done} aria-label={L(ui.done)}>
                {busy === q.id ? "…" : (done ? "✓" : "")}
              </button>
              <div className="mission-body">
                <div className="mission-label">{L(q.label)}</div>
                {flash && flash.id === q.id && (
                  <div className="mission-praise">✦ {flash.text}</div>
                )}
              </div>
              <span className="mission-lp">+{q.lp} {L(ui.pts)}</span>
            </div>
          );
        })}
      </div>
    </div>
  );
}
window.Missions = Missions;

// ── Insights page (TICKET-034/035/036/037/038/040) ──
function Insights({ t, locale, onBack }) {
  const L = (o) => o[locale] || o.en;
  const ui = {
    title:    { en: "Your insights", ru: "Твои инсайты", uk: "Твої інсайти", kk: "Сенің инсайттарың", uz: "Sening insaytlaring", es: "Tus ideas", fr: "Tes idées", hy: "Քո գաղափարները" },
    lede:     { en: "After a lesson, capture the spark. Share it — and inspire someone to start.", ru: "После урока — запиши озарение. Поделись им — и вдохнови кого-то начать.", uk: "Після уроку — запиши осяяння. Поділись ним — і надихни когось почати.", kk: "Сабақтан кейін — ойыңды жаз. Бөліс — біреуді бастауға шабыттандыр.", uz: "Darsdan keyin — fikringni yoz. Ulash — kimnidir boshlashga ilhomlantir.", es: "Después de una lección, captura la chispa. Compártela e inspira a alguien a empezar.", fr: "Après une leçon, capture l'étincelle. Partage-la — et inspire quelqu'un à commencer.", hy: "Դասից հետո՝ որսա կայծը։ Կիսվիր դրանով և ոգեշնչիր ինչ-որ մեկին սկսել։" },
    back:     { en: "← Account", ru: "← Кабинет", uk: "← Кабінет", kk: "← Кабинет", uz: "← Kabinet", es: "← Cuenta", fr: "← Compte", hy: "← Հաշիվ" },
    capH:     { en: "Capture an insight", ru: "Запиши инсайт", uk: "Запиши інсайт", kk: "Инсайт жаз", uz: "Insayt yoz", es: "Captura una idea", fr: "Capture une idée", hy: "Որսա գաղափար" },
    q:        { en: "What clicked? What surprised you? What did you learn?", ru: "Что зацепило? Что удивило? Что нового узнал?", uk: "Що зачепило? Що здивувало? Що нового дізнався?", kk: "Не әсер етті? Не таңғалдырды? Не білдің?", uz: "Nima taʼsir qildi? Nima hayratlantirdi? Nima oʻrganding?", es: "¿Qué te hizo clic? ¿Qué te sorprendió? ¿Qué aprendiste?", fr: "Qu'est-ce qui a fait tilt ? Qu'est-ce qui t'a surpris ? Qu'as-tu appris ?", hy: "Ի՞նչը պարզ դարձավ։ Ի՞նչը զարմացրեց։ Ի՞նչ սովորեցիր։" },
    ph:       { en: "I didn't realise that…", ru: "Я не знал, что…", uk: "Я не знав, що…", kk: "Мен білмедім, …", uz: "Men bilmasdimki…", es: "No me había dado cuenta de que…", fr: "Je ne savais pas que…", hy: "Ես չգիտեի, որ…" },
    save:     { en: "Save insight", ru: "Сохранить инсайт", uk: "Зберегти інсайт", kk: "Инсайтты сақтау", uz: "Insaytni saqlash", es: "Guardar idea", fr: "Sauvegarder l'idée", hy: "Պահել գաղափարը" },
    shareQ:   { en: "Share this insight publicly?", ru: "Поделиться этим инсайтом публично?", uk: "Поділитися цим інсайтом публічно?", kk: "Бұл инсайтты ашық бөлісесің бе?", uz: "Bu insaytni ommaviy ulashasanmi?", es: "¿Compartir esta idea públicamente?", fr: "Partager cette idée publiquement ?", hy: "Կիսվե՞լ այս գաղափարով հրապարակավ" },
    shareNote:{ en: "Optional. Pick up to 2 networks you actually use. +points for sharing.", ru: "По желанию. Выбери до 2 сетей, которыми реально пользуешься. +баллы за шеринг.", uk: "За бажанням. Обери до 2 мереж, якими реально користуєшся. +бали за шеринг.", kk: "Қаласаң. Шынымен қолданатын 2 желіні таңда. Бөліскенге +ұпай.", uz: "Ixtiyoriy. Haqiqatan ishlatadigan 2 tarmoqni tanla. Ulashganga +ball.", es: "Opcional. Elige hasta 2 redes que realmente uses. +puntos por compartir.", fr: "Facultatif. Choisis jusqu'à 2 réseaux que tu utilises vraiment. +points pour le partage.", hy: "Ըստ ցանկության։ Ընտրիր մինչև 2 ցանց, որ իրականում օգտագործում ես։ +միավորներ կիսվելու համար։" },
    genLink:  { en: "Generate a share link", ru: "Сгенерировать ссылку", uk: "Згенерувати посилання", kk: "Сілтеме жасау", uz: "Havola yaratish", es: "Generar un enlace para compartir", fr: "Générer un lien de partage", hy: "Ստեղծել կիսվելու հղում" },
    orPaste:  { en: "or paste your public post link", ru: "или вставь ссылку на свой публичный пост", uk: "або встав посилання на свій публічний пост", kk: "немесе өз ашық постыңның сілтемесін қой", uz: "yoki oʻz ommaviy postingga havola qoʻy", es: "o pega el enlace de tu publicación pública", fr: "ou colle le lien de ta publication publique", hy: "կամ տեղադրիր քո հրապարակային գրառման հղումը" },
    pastePh:  { en: "https://t.me/…", ru: "https://t.me/…", uk: "https://t.me/…", kk: "https://t.me/…", uz: "https://t.me/…", es: "https://t.me/…", fr: "https://t.me/…", hy: "https://t.me/…" },
    confirm:  { en: "Confirm share", ru: "Подтвердить шеринг", uk: "Підтвердити шеринг", kk: "Бөлісуді растау", uz: "Ulashishni tasdiqlash", es: "Confirmar compartir", fr: "Confirmer le partage", hy: "Հաստատել կիսվելը" },
    copied:   { en: "Link copied", ru: "Ссылка скопирована", uk: "Посилання скопійовано", kk: "Сілтеме көшірілді", uz: "Havola nusxalandi", es: "Enlace copiado", fr: "Lien copié", hy: "Հղումը պատճենվեց" },
    savedThanks:{ en: "Saved. This becomes part of the shared mind.", ru: "Сохранено. Это становится частью общего разума.", uk: "Збережено. Це стає частиною спільного розуму.", kk: "Сақталды. Бұл ортақ ақылдың бөлігі болады.", uz: "Saqlandi. Bu umumiy ongning bir qismiga aylanadi.", es: "Guardado. Esto pasa a formar parte de la mente compartida.", fr: "Sauvegardé. Cela rejoint l'esprit partagé.", hy: "Պահված է։ Սա դառնում է ընդհանուր մտքի մաս։" },
    shared:   { en: "Shared — thank you for spreading the spark.", ru: "Опубликовано — спасибо, что несёшь искру дальше.", uk: "Опубліковано — дякуємо, що несеш іскру далі.", kk: "Жарияланды — ұшқынды әрі қарай жеткізгеніңе рахмет.", uz: "Eʼlon qilindi — uchqunni yoygʻaning uchun rahmat.", es: "Compartido: gracias por difundir la chispa.", fr: "Partagé — merci de propager l'étincelle.", hy: "Կիսվեց՝ շնորհակալություն կայծը տարածելու համար։" },
    saveFail: { en: "Couldn't save. Try again.", ru: "Не удалось сохранить. Попробуй ещё раз.", uk: "Не вдалося зберегти. Спробуй ще раз.", kk: "Сақтау сәтсіз. Қайта көр.", uz: "Saqlab boʻlmadi. Yana urinib koʻr.", es: "No se pudo guardar. Inténtalo de nuevo.", fr: "Impossible de sauvegarder. Réessaie.", hy: "Չհաջողվեց պահել։ Փորձիր նորից։" },
    shareFail:{ en: "Couldn't publish. Try again.", ru: "Не удалось опубликовать. Попробуй ещё раз.", uk: "Не вдалося опублікувати. Спробуй ще раз.", kk: "Жариялау сәтсіз. Қайта көр.", uz: "Eʼlon qilib boʻlmadi. Yana urinib koʻr.", es: "No se pudo publicar. Inténtalo de nuevo.", fr: "Impossible de publier. Réessaie.", hy: "Չհաջողվեց հրապարակել։ Փորձիր նորից։" },
    mine:     { en: "Saved by you", ru: "Сохранённые тобой", uk: "Збережені тобою", kk: "Сен сақтағандар", uz: "Sen saqlaganlar", es: "Guardado por ti", fr: "Sauvegardé par toi", hy: "Քո պահածները" },
    streamH:  { en: "Shared mind — anonymous", ru: "Общий разум — анонимно", uk: "Спільний розум — анонімно", kk: "Ортақ ақыл — жасырын", uz: "Umumiy ong — anonim", es: "Mente compartida — anónima", fr: "Esprit partagé — anonyme", hy: "Ընդհանուր միտք՝ անանուն" },
    streamNote:{ en: "Insights from everyone, no names. Read what the new internet taught others.", ru: "Инсайты всех, без имён. Читай, чему новый интернет научил других.", uk: "Інсайти всіх, без імен. Читай, чого новий інтернет навчив інших.", kk: "Бәрінің инсайттары, есімсіз. Жаңа интернет басқаларды неге үйреткенін оқы.", uz: "Hammaning insaytlari, ismsiz. Yangi internet boshqalarni nimaga oʻrgatganini oʻqi.", es: "Ideas de todos, sin nombres. Lee lo que el nuevo internet enseñó a otros.", fr: "Les idées de tous, sans noms. Lis ce que le nouvel internet a appris aux autres.", hy: "Բոլորի գաղափարները՝ առանց անունների։ Կարդա, ինչ նոր ինտերնետը սովորեցրեց ուրիշներին։" },
  };
  const SOCIALS = [
    { id: "telegram", label: "Telegram" }, { id: "facebook", label: "Facebook" },
    { id: "x", label: "X / Twitter" }, { id: "linkedin", label: "LinkedIn" },
    { id: "instagram", label: "Instagram" },
  ];

  const [text, setText] = useState_e("");
  const [stage, setStage] = useState_e("capture"); // capture → share → done
  const [picked, setPicked] = useState_e([]);       // up to 2 socials
  const [ownLink, setOwnLink] = useState_e("");
  const [copied, setCopied] = useState_e(false);
  const [mine, setMine] = useState_e([]);
  const [createdId, setCreatedId] = useState_e(null);   // FE-2: real server insight id (not Date.now())
  const [saveErr, setSaveErr]   = useState_e("");
  const [shareErr, setShareErr] = useState_e("");

  // S2-BIG.4: live anonymous feed + create + "saved by you" (demo keeps the seed).
  const DEMO = (typeof window !== "undefined" && window.LUMINARA_DEMO !== false);
  const api = (typeof window !== "undefined") ? window.LUMINARA_API : null;
  const activationLocked = !DEMO && !!(typeof window !== "undefined" && window.LUMINARA_ACTIVATION && !window.LUMINARA_ACTIVATION.activated);
  const NODES = (window.LUMINARA_DATA && window.LUMINARA_DATA.NODES) || [];
  const [topic, setTopic] = useState_e((NODES[0] && NODES[0].id) || "general");
  const [feed, setFeed] = useState_e(null); // null until loaded (real mode)
  useEffect_e(() => {
    if (DEMO || !api) return;
    let alive = true;
    (async () => {
      try {
        const f = await api.insights.feed({ limit: 30 });   // public, auth:false, anonymous
        if (alive) setFeed((f && f.insights) || []);
      } catch (e) { if (alive) setFeed([]); }
      try {
        const m = await api.insights.mine();                // { insights[] }
        if (alive && m && Array.isArray(m.insights)) {
          setMine(m.insights.map(it => ({ id: it.id, text: it.body, shared: it.status === "published" })));
        }
      } catch (e) { /* non-fatal */ }
    })();
    return () => { alive = false; };
  }, []);

  const toggleSocial = (id) => {
    setPicked(p => p.includes(id) ? p.filter(x => x !== id)
      : (p.length >= 2 ? p : [...p, id]));   // max 2 (TICKET-037)
  };
  const resetFlow = () => { setStage("capture"); setText(""); setPicked([]); setOwnLink(""); setCreatedId(null); };

  // FE-2: real create. Capture the server-issued insight id (no fake Date.now() id);
  // on failure surface an error instead of faking a save.
  const saveInsight = async () => {
    if (!text.trim()) return;
    const body = text.trim();
    if (DEMO || !api) {
      setMine(m => [{ id: "demo-" + Date.now(), text: body, shared: false }, ...m]);
      setCreatedId(null); setStage("share"); return;
    }
    setSaveErr("");
    try {
      const r = await api.insights.create(topic, body);   // { insight } — enters moderation
      const ins = r && r.insight;
      if (!ins || ins.id == null) throw new Error("no insight id");
      setCreatedId(ins.id);
      setMine(m => [{ id: ins.id, text: ins.body, shared: ins.status === "published" }, ...m]);
      setStage("share");
    } catch (e) {
      setSaveErr(e && e.status === 423
        ? (locale === "ru" ? "Заверши первую сцену, квиз и подтверди TON-кошелёк." : "Complete the first scene, quiz and verified TON wallet first.")
        : L(ui.saveFail));   // honest: stay on capture, no fake success
    }
  };

  // FE-2: a real, working share link — the user's referral deep-link (drives real
  // installs and credits the sharer), not a placeholder luminara.app/in/<random>.
  const genLink = async () => {
    let url = "";
    try {
      if (api) { const r = await api.referral.code(); if (r && r.url) url = r.url; }
    } catch (e) { /* fall through to same-origin */ }
    if (!url) url = window.location.origin + window.location.pathname;
    try {
      await navigator.clipboard.writeText(url);
      setCopied(true); setTimeout(() => setCopied(false), 1600);
    } catch (e) {
      try { window.prompt("Link", url); } catch (e2) {}   // no fake "copied ✓"
    }
  };

  // FE-2: actually publish via PATCH /insights/:id { shared:true } (server moves it to
  // moderation + awards insight_shared). "Shared" shows only after the server confirms.
  const confirmShare = async () => {
    if (DEMO || !api || createdId == null) {
      setMine(m => m.map((it, i) => i === 0 ? { ...it, shared: true } : it));
      setStage("done"); setTimeout(resetFlow, 2400); return;
    }
    setShareErr("");
    try {
      const fields = { shared: true };
      if (ownLink.trim()) fields.share_links = { link: ownLink.trim() };
      await api.insights.update(createdId, fields);   // { insight }
      setMine(m => m.map(it => it.id === createdId ? { ...it, shared: true } : it));
      setStage("done"); setTimeout(resetFlow, 2400);
    } catch (e) {
      setShareErr(L(ui.shareFail));   // honest: stay on share, no fake "shared"
    }
  };

  return (
    <div className="section-pad fade-in">
      <button className="eco-back" onClick={onBack}>{L(ui.back)}</button>
      <div className="section-head">
        <div className="kicker">{t.nav?.account || "Account"} · {L(ui.title)}</div>
        <h2>{L(ui.title)}</h2>
        <p>{L(ui.lede)}</p>
      </div>

      {/* Capture / share flow */}
      <div className="ins-capture">
        {stage === "capture" && (
          <React.Fragment>
            <div className="ins-cap-h">{L(ui.capH)}</div>
            <div className="ins-q">{L(ui.q)}</div>
            {NODES.length > 0 && (
              <select className="acc-input" style={{ marginBottom: 10 }} value={topic} onChange={e => setTopic(e.target.value)}>
                {NODES.map(n => <option key={n.id} value={n.id}>{(n.title && (n.title[locale] || n.title.en)) || n.id}</option>)}
              </select>
            )}
            <textarea className="ins-textarea" rows={3} placeholder={L(ui.ph)}
                      disabled={activationLocked} value={text} onChange={e => setText(e.target.value)} />
            <button className="btn primary" disabled={!text.trim() || activationLocked} onClick={saveInsight}>{L(ui.save)}</button>
            {activationLocked && <div className="acc-lock-note">🔒 {locale === "ru" ? "Инсайты откроются после активации." : "Insights unlock after activation."}</div>}
            {saveErr && <div className="ins-err" style={{ color: "var(--c-crimson, #e5484d)", marginTop: 8, fontSize: ".85rem" }}>{saveErr}</div>}
          </React.Fragment>
        )}
        {stage === "share" && (
          <React.Fragment>
            <div className="ins-cap-h">{L(ui.shareQ)}</div>
            <div className="ins-q">{L(ui.shareNote)}</div>
            <div className="ins-socials">
              {SOCIALS.map(s => (
                <button key={s.id}
                        className={"ins-social" + (picked.includes(s.id) ? " on" : "")}
                        disabled={!picked.includes(s.id) && picked.length >= 2}
                        onClick={() => toggleSocial(s.id)}>{s.label}</button>
              ))}
            </div>
            <div className="ins-share-row">
              <button className="btn" onClick={genLink}>{copied ? L(ui.copied) + " ✓" : L(ui.genLink)}</button>
            </div>
            <div className="ins-or">{L(ui.orPaste)}</div>
            <input className="acc-input" placeholder={L(ui.pastePh)} value={ownLink} onChange={e => setOwnLink(e.target.value)} />
            <button className="btn primary" style={{ marginTop: 12 }}
                    disabled={picked.length === 0}
                    onClick={confirmShare}>{L(ui.confirm)}</button>
            {shareErr && <div className="ins-err" style={{ color: "var(--c-crimson, #e5484d)", marginTop: 8, fontSize: ".85rem" }}>{shareErr}</div>}
          </React.Fragment>
        )}
        {stage === "done" && (
          <div className="ins-done">✦ {ownLink || picked.length ? L(ui.shared) : L(ui.savedThanks)}</div>
        )}
      </div>

      {/* Your saved insights */}
      {mine.length > 0 && (
        <div className="ins-mine">
          <div className="ins-section-h">{L(ui.mine)}</div>
          {mine.map(it => (
            <div key={it.id} className="ins-mine-item">
              <span className="ins-mine-text">“{it.text}”</span>
              {it.shared && <span className="ins-shared-badge">✓</span>}
            </div>
          ))}
        </div>
      )}

      {/* Shared anonymous stream (TICKET-040) */}
      <div className="ins-stream">
        <div className="ins-section-h ins-h-row">
          <span>{L(ui.streamH)}</span>
        </div>
        <div className="acc-sub" style={{ marginBottom: 14 }}>{L(ui.streamNote)}</div>
        <div className="ins-stream-grid">
          {((!DEMO && Array.isArray(feed))
              ? feed.map(it => ({ topic: it.topic, body: it.body }))
              : window.LUMINARA_INSIGHTS_SEED.map(it => ({ topic: it.topic, body: L(it.text) }))
           ).map((it, i) => (
            <div key={i} className="ins-stream-card">
              <span className="ins-topic">#{it.topic}</span>
              <span className="ins-stream-text">“{it.body}”</span>
            </div>
          ))}
        </div>
      </div>
    </div>
  );
}
window.Insights = Insights;

// QUIZ-CATALOG (#79): the server inventories every published quiz and decides
// entitlement. This component only presents its safe metadata and opens a quiz
// after the server marks it unlocked.
function QuizzesCatalog({ t, locale, authRevision, onOpenAccount }) {
  const L = (o) => (o && (o[locale] || o.en || o.ru)) || '';
  const initialTopic = (typeof window !== 'undefined' && window.LUM_ROUTE) ? window.LUM_ROUTE.get('topic') : null;
  const [selected, setSelected] = useState_e(initialTopic || null);
  const [state, setState] = useState_e({ loading: true, error: '', data: null });
  const ui = {
    kicker: { en: "Quizzes", ru: "Квизы", uk: "Квізи", kk: "Квиздер", uz: "Kvizlar",
              es: "Cuestionarios", fr: "Quiz", hy: "Վիկտորինաներ" },
    title:  { en: "Quiz catalog", ru: "Каталог квизов", uk: "Каталог квізів", kk: "Квиздер каталогы", uz: "Kvizlar katalogi", es: "Catálogo de cuestionarios", fr: "Catalogue des quiz", hy: "Վիկտորինաների կատալոգ" },
    lede:   { en: "All published quizzes in one place. Your access and progress are checked by the server.",
              ru: "Все опубликованные квизы в одном месте. Доступ и прогресс проверяет сервер.",
              uk: "Усі опубліковані квізи в одному місці. Доступ і прогрес перевіряє сервер." },
    loading: { en: "Loading quizzes…", ru: "Загружаем квизы…", uk: "Завантажуємо квізи…" },
    retry: { en: "Try again", ru: "Повторить", uk: "Повторити" },
    unavailable: { en: "The quiz catalog is temporarily unavailable.", ru: "Каталог квизов временно недоступен.", uk: "Каталог квізів тимчасово недоступний." },
    questions: { en: "questions", ru: "вопросов", uk: "питань", kk: "сұрақ", uz: "savol", es: "preguntas", fr: "questions", hy: "հարց" },
    open: { en: "Open quiz", ru: "Открыть квиз", uk: "Відкрити квіз", kk: "Квизді ашу", uz: "Kvizni ochish", es: "Abrir cuestionario", fr: "Ouvrir le quiz", hy: "Բացել վիկտորինան" },
    unlock: { en: "View subscription", ru: "Открыть подписку", uk: "Переглянути підписку", kk: "Жазылымды көру", uz: "Obunani ko‘rish", es: "Ver suscripción", fr: "Voir l’abonnement", hy: "Դիտել բաժանորդագրությունը" },
    progress: { en: "answered", ru: "отвечено", uk: "відповіді", kk: "жауап", uz: "javob", es: "respondidas", fr: "répondues", hy: "պատասխանված" },
    missingTitle: { en: "Title pending review", ru: "Название ожидает проверки", uk: "Назва очікує перевірки" },
    activation: { en: "Activation", ru: "Активация", uk: "Активація", kk: "Белсендіру", uz: "Faollashtirish", es: "Activación", fr: "Activation", hy: "Ակտիվացում" },
  };
  // Russian plural forms are part of the rendered catalog, not a fixed label:
  // 1 вопрос, 2–4 вопроса, 5+ вопросов (including 11–14 вопросов).
  const questionLabel = (count) => {
    if (locale !== 'ru') return L(ui.questions);
    const value = Math.abs(Number(count) || 0);
    const lastTwo = value % 100;
    const last = value % 10;
    if (lastTwo >= 11 && lastTwo <= 14) return 'вопросов';
    if (last === 1) return 'вопрос';
    if (last >= 2 && last <= 4) return 'вопроса';
    return 'вопросов';
  };

  const load = () => {
    const api = window.LUMINARA_API;
    if (!api || !api.quiz || !api.quiz.catalog) {
      setState({ loading: false, error: 'api', data: null });
      return;
    }
    let active = true;
    setState((s) => ({ ...s, loading: true, error: '' }));
    api.quiz.catalog().then((data) => {
      if (active) setState({ loading: false, error: '', data });
    }).catch(() => {
      if (active) setState({ loading: false, error: 'network', data: null });
    });
    return () => { active = false; };
  };
  useEffect_e(load, [authRevision]);
  useEffect_e(() => {
    if (typeof window !== 'undefined' && window.LUM_ROUTE) window.LUM_ROUTE.set('topic', selected);
  }, [selected]);

  if (selected) {
    return (
      <div className="section-pad fade-in quiz-catalog-view">
        <button className="back-btn" onClick={() => setSelected(null)}>← {L(ui.title)}</button>
        <div className="quiz-hub">
          {window.ThemeQuiz ? <window.ThemeQuiz topic={selected} locale={locale} onBackToChapter={() => setSelected(null)} /> : null}
        </div>
      </div>
    );
  }

  const quizzes = (state.data && Array.isArray(state.data.quizzes)) ? state.data.quizzes : [];
  const groups = quizzes.reduce((out, quiz) => {
    (out[quiz.section_key] = out[quiz.section_key] || []).push(quiz);
    return out;
  }, {});
  const sectionLabel = (key) => {
    if (key === 'foundations') return (t.nav && t.nav.foundations) || key;
    if (key === 'activation') return L(ui.activation);
    const node = window.LUMINARA_DATA && (window.LUMINARA_DATA.NODES || []).find((item) => item.id === key);
    return node ? L(node.title) : key;
  };
  const accessLabel = (access) => (typeof window.luminaraAccessLabel === 'function')
    ? window.luminaraAccessLabel(locale, access)
    : access;
  return (
    <div className="section-pad fade-in">
      <div className="section-head">
        <div className="kicker">{L(ui.kicker)}</div>
        <h2>{L(ui.title)}</h2>
        <p>{L(ui.lede)}</p>
      </div>
      {state.loading && <div className="quiz-catalog-state" role="status">{L(ui.loading)}</div>}
      {!state.loading && state.error && (
        <div className="quiz-catalog-state" role="alert">
          <span>{L(ui.unavailable)}</span>
          <button className="btn" onClick={load}>{L(ui.retry)}</button>
        </div>
      )}
      {!state.loading && !state.error && Object.entries(groups).map(([section, items]) => (
        <section className="quiz-catalog-section" key={section}>
          <h3>{sectionLabel(section)}</h3>
          <div className="quiz-catalog-grid">
            {items.map((quiz) => {
              const title = L(quiz.title) || quiz.topic_key;
              return (
                <article className={'quiz-catalog-card' + (quiz.locked ? ' locked' : '')} key={quiz.topic_key}>
                  <div className="quiz-catalog-card-top">
                    <span className="quiz-access-badge">{accessLabel(quiz.access)}</span>
                    <span className="mono">{quiz.question_count} {questionLabel(quiz.question_count)}</span>
                  </div>
                  <h4>{title}</h4>
                  {quiz.title_status === 'missing' && <div className="quiz-title-warning">{L(ui.missingTitle)} · {quiz.topic_key}</div>}
                  <div className="quiz-card-progress">{quiz.stats.answered} / {quiz.question_count} · {L(ui.progress)}</div>
                  <button className="btn quiz-card-action" onClick={() => quiz.locked ? onOpenAccount() : setSelected(quiz.topic_key)}>
                    {quiz.locked ? '🔒 ' + L(ui.unlock) : L(ui.open) + ' →'}
                  </button>
                </article>
              );
            })}
          </div>
        </section>
      ))}
    </div>
  );
}
window.QuizzesCatalog = QuizzesCatalog;
