const { useState, useEffect, useMemo, useRef, Component } = React;
  const tg = window.Telegram && window.Telegram.WebApp ? window.Telegram.WebApp : null;
  if (tg) tg.expand();

  if (tg && tg.initData) {
    axios.defaults.headers.common['x-telegram-init-data'] = tg.initData;
  }

  const DICT = {
    ru: {
      loading: "ЗАГРУЗКА HEDGE PRO...", waiting: "ОЖИДАНИЕ ДАННЫХ...",
      access_denied: "ДОСТУП ЗАКРЫТ", sub_required: "Для использования терминала оформите подписку",
      trial_btn: "🎁 ПОПРОБОВАТЬ БЕСПЛАТНО (24ч)", pay_btn: "ВЫБРАТЬ ТАРИФ",
      tariffs: "ТАРИФЫ", close: "ЗАКРЫТЬ", profile: "ПРОФИЛЬ", bl: "ЧС",
      min_p: "Мин %", max_p: "Макс %", vol: "Объем $",
      buy: "КУПИТЬ", sell: "ПРОДАТЬ", search: "ПОИСК ПРИБЫЛЬНЫХ СВЯЗОК...",
      settings: "НАСТРОЙКИ", sound: "Звук уведомлений", save: "СОХРАНИТЬ",
      on: "ВКЛ", off: "ВЫКЛ",
      s2f: "Связка: Spot + Fut", f2f: "Связка: Fut + Fut", s2s: "Связка: Spot + Spot",
      empty: "Список пуст", return: "ВЕРНУТЬ",
      admin_panel: "ПАНЕЛЬ АДМИНИСТРАТОРА", total_users: "Всего юзеров",
      active_subs: "Активных подписок", online: "Онлайн",
      sub_active: "СТАТУС: ПОДПИСКА АКТИВНА", days_left: "Осталось дней:", end_date: "Дата окончания:",
      guide: "ГАЙД", referral_short: "РЕФ. ПРОГРАММА",
      notif_title: "УВЕДОМЛЕНИЯ", notif_conv: "Схождение спреда", notif_div: "Расхождение спреда",
      spot_blocked: "SPOT→SPOT ОТКЛЮЧЕНА: ввод/вывод монеты закрыт на одной из бирж"
    },
    en: {
      loading: "LOADING HEDGE PRO...", waiting: "WAITING FOR DATA...",
      access_denied: "ACCESS DENIED", sub_required: "Purchase a subscription to use the terminal",
      trial_btn: "🎁 TRY FOR FREE (24h)", pay_btn: "CHOOSE PLAN",
      tariffs: "PLANS", close: "CLOSE", profile: "PROFILE", bl: "BL",
      min_p: "Min %", max_p: "Max %", vol: "Volume $",
      buy: "BUY", sell: "SELL", search: "SEARCHING FOR OPPORTUNITIES...",
      settings: "SETTINGS", sound: "Notification Sound", save: "SAVE",
      on: "ON", off: "OFF",
      s2f: "Pair: Spot + Fut", f2f: "Pair: Fut + Fut", s2s: "Pair: Spot + Spot",
      empty: "List is empty", return: "RESTORE",
      admin_panel: "ADMIN PANEL", total_users: "Total Users",
      active_subs: "Active Subs", online: "Online",
      sub_active: "STATUS: SUBSCRIPTION ACTIVE", days_left: "Days left:", end_date: "End date:",
      guide: "GUIDE", referral_short: "REFERRAL",
      notif_title: "NOTIFICATIONS", notif_conv: "Spread Convergence", notif_div: "Spread Divergence",
      spot_blocked: "SPOT→SPOT DISABLED: deposit/withdraw is closed on one exchange"
    }
  };

  class ErrorBoundary extends Component {
    constructor(props) { super(props); this.state = { hasError: false, error: null }; }
    static getDerivedStateFromError(error) { return { hasError: true, error: error }; }
    render() {
      if (this.state.hasError) return (
        <div style={{padding:'20px', color:'#ff3b3b'}}>
          <h3>ОШИБКА ИНТЕРФЕЙСА:</h3>
          <pre>{this.state.error.toString()}</pre>
        </div>
      );
      return this.props.children;
    }
  }

  const MatrixIntro = () => {
    const canvasRef = useRef(null);
    useEffect(() => {
      const canvas = canvasRef.current; const ctx = canvas.getContext('2d');
      canvas.width = window.innerWidth; canvas.height = window.innerHeight;
      const icons = ["₿","Ξ","₮","◎","✕","⟠","L","S","M"];
      const drops = Array(Math.floor(canvas.width / 30)).fill(1);
      const draw = () => {
        ctx.fillStyle = "rgba(0, 0, 0, 0.1)"; ctx.fillRect(0, 0, canvas.width, canvas.height);
        ctx.font = "22px serif";
        drops.forEach((y, i) => {
          ctx.fillStyle = "#00d4ff"; ctx.fillText(icons[Math.floor(Math.random()*icons.length)], i * 35, y * 35);
          if (y * 35 > canvas.height && Math.random() > 0.975) drops[i] = 0;
          drops[i]++;
        });
      };
      const int = setInterval(draw, 70); return () => clearInterval(int);
    }, []);
    return <canvas ref={canvasRef} />;
  };

  const App = () => {
    const EXCHANGES = ['Binance','Bybit','Gate','MEXC','KuCoin','Bitget','BingX'];
    const S2S_MIN_VOLUME_USDT = 50000;
    const safeParse = (key, fallback) => { try { const item = localStorage.getItem(key); return item ? JSON.parse(item) : fallback; } catch(e) { return fallback; } };

    const getAlertSpreadBounds = (alert) => {
      let from = Number(alert && (alert.spreadFrom != null ? alert.spreadFrom : alert.targetSpread));
      const rawTo = alert ? alert.spreadTo : null;
      let to = rawTo === '' || rawTo == null ? null : Number(rawTo);

      from = Number.isFinite(from) ? Math.max(0, from) : 0;
      to = Number.isFinite(to) ? to : null;
      if (to != null && to < 0) to = null;

      if (to != null && from > 0 && to === 0) to = null;

      if (to != null && to < from) {
        const low = Math.min(from, to);
        const high = Math.max(from, to);
        from = low;
        to = high;
      }

      return { from, to };
    };


    const formatPercentCompact = (value) => {
      const num = Number(value);
      if (!Number.isFinite(num)) return '0%';
      const rounded = Math.round(num * 100) / 100;
      const text = Number.isInteger(rounded) ? String(rounded) : String(rounded).replace(/\.0+$/, '').replace(/(\.\d*?)0+$/, '$1');
      return text + '%';
    };

    const formatAlertRangeLabel = (alert) => {
      const bounds = getAlertSpreadBounds(alert);
      const direction = String((alert && alert.direction) || '').toLowerCase();
      if (direction === 'divergence') {
        if (bounds.to != null) return 'от ' + formatPercentCompact(bounds.from) + ' до ' + formatPercentCompact(bounds.to);
        return 'от ' + formatPercentCompact(bounds.from);
      }
      if (bounds.to != null) return 'от ' + formatPercentCompact(bounds.from) + ' до ' + formatPercentCompact(bounds.to);
      return 'до ' + formatPercentCompact(bounds.from);
    };


    const [lang, setLang] = useState(() => localStorage.getItem('h_lang') || 'ru');
    const t = DICT[lang];

    const [authStatus, setAuthStatus] = useState('loading');
    const [userData, setUserData] = useState(null);
    const [data, setData] = useState(null);

    const [networks, setNetworks] = useState({});
    const [spotDepthChecks, setSpotDepthChecks] = useState({});
    const [activeS2S, setActiveS2S] = useState({});
    const [visibleS2S, setVisibleS2S] = useState([]);
    const [serverS2S, setServerS2S] = useState([]);
    const [showPrices, setShowPrices] = useState(false);
    const [trialUsed, setTrialUsed] = useState(() => localStorage.getItem('h_trial_used') === 'true');

    const readScanProfile = (mode) => {
      const prefix = mode === 's2s' ? 'h_s2s_' : 'h_main_';
      const fallbackMinS = Number(localStorage.getItem('h_min_s')) || (mode === 's2s' ? 0.3 : 0.5);
      const fallbackMaxS = Number(localStorage.getItem('h_max_s')) || (mode === 's2s' ? 100 : 25);
      const fallbackMinV = Number(localStorage.getItem('h_min_v')) || (mode === 's2s' ? 3000 : 100000);
      const fallbackTradeUsd = Number(localStorage.getItem('h_trade_usd')) || (mode === 's2s' ? 100 : 1000);
      return {
        minS: Number(localStorage.getItem(prefix + 'min_s')) || fallbackMinS,
        maxS: Number(localStorage.getItem(prefix + 'max_s')) || fallbackMaxS,
        minV: Number(localStorage.getItem(prefix + 'min_v')) || fallbackMinV,
        tradeUsd: Number(localStorage.getItem(prefix + 'trade_usd')) || fallbackTradeUsd,
        activeExs: safeParse(prefix + 'exs', safeParse('h_exs', EXCHANGES)),
        bl: safeParse(prefix + 'bl', safeParse('h_bl', [])),
      };
    };

    const saveScanProfile = (mode, values) => {
      const prefix = mode === 's2s' ? 'h_s2s_' : 'h_main_';
      localStorage.setItem(prefix + 'min_s', values.minS);
      localStorage.setItem(prefix + 'max_s', values.maxS);
      localStorage.setItem(prefix + 'min_v', values.minV);
      localStorage.setItem(prefix + 'trade_usd', values.tradeUsd);
      localStorage.setItem(prefix + 'exs', JSON.stringify(values.activeExs));
      localStorage.setItem(prefix + 'bl', JSON.stringify(values.bl));
    };

    const initialS2S = localStorage.getItem('h_s2s') !== 'false';
    const initialProfile = readScanProfile(initialS2S ? 's2s' : 'main');

    const [arbS2F, setArbS2F] = useState(() => !initialS2S && localStorage.getItem('h_s2f') !== 'false');
    const [arbF2F, setArbF2F] = useState(() => !initialS2S && localStorage.getItem('h_f2f') !== 'false');
    const [arbS2S, setArbS2S] = useState(() => initialS2S);

    const [minS, setMinS] = useState(() => initialProfile.minS);
    const [maxS, setMaxS] = useState(() => initialProfile.maxS);
    const [minV, setMinV] = useState(() => initialProfile.minV);
    const [tradeUsd, setTradeUsd] = useState(() => initialProfile.tradeUsd);

    const [activeExs, setActiveExs] = useState(() => initialProfile.activeExs);
    const [bl, setBl] = useState(() => initialProfile.bl);

    const applyScanProfile = (mode) => {
      const profile = readScanProfile(mode);
      setMinS(profile.minS);
      setMaxS(profile.maxS);
      setMinV(profile.minV);
      setTradeUsd(profile.tradeUsd);
      setActiveExs(profile.activeExs);
      setBl(profile.bl);
    };

    const persistCurrentScanProfile = (mode) => {
      saveScanProfile(mode, { minS, maxS, minV, tradeUsd, activeExs, bl });
    };

    const switchS2SMode = () => {
      const currentMode = arbS2S ? 's2s' : 'main';
      persistCurrentScanProfile(currentMode);
      const next = !arbS2S;
      setArbS2S(next);
      if (next) {
        setArbS2F(false);
        setArbF2F(false);
        applyScanProfile('s2s');
      } else {
        applyScanProfile('main');
      }
    };

    const switchS2FMode = () => {
      const currentMode = arbS2S ? 's2s' : 'main';
      persistCurrentScanProfile(currentMode);
      const next = !arbS2F;
      if (arbS2S) applyScanProfile('main');
      setArbS2S(false);
      setArbS2F(next);
    };

    const switchF2FMode = () => {
      const currentMode = arbS2S ? 's2s' : 'main';
      persistCurrentScanProfile(currentMode);
      const next = !arbF2F;
      if (arbS2S) applyScanProfile('main');
      setArbS2S(false);
      setArbF2F(next);
    };

    const [showNotify, setShowNotify] = useState(false);
    const [isSound, setIsSound] = useState(() => localStorage.getItem('h_sound') !== 'false');
    const [notifConv, setNotifConv] = useState(() => localStorage.getItem('h_notif_conv') !== 'false');
    const [notifDiv, setNotifDiv] = useState(() => localStorage.getItem('h_notif_div') !== 'false');
    const [userAlerts, setUserAlerts] = useState([]);
    const [alertBlacklist, setAlertBlacklist] = useState([]);
    const [alertBlacklistInput, setAlertBlacklistInput] = useState('');
    const [alertBusy, setAlertBusy] = useState(false);
    const [convAlertForm, setConvAlertForm] = useState({ coin: '', spreadFrom: 1, spreadTo: '', buyExchange: 'Binance', sellExchange: 'Bybit', buyMarketType: 'S' });
    const [divAlertForm, setDivAlertForm] = useState({
      coinMode: 'all',
      coin: '',
      spreadFrom: 1,
      spreadTo: '',
      buyMarketTypes: ['S'],
      buyExchanges: ['Binance'],
      sellExchanges: ['Bybit'],
    });
    const [adminGuardSymbols, setAdminGuardSymbols] = useState([]);
    const [adminGuardInput, setAdminGuardInput] = useState('');
    const [adminGuardBusy, setAdminGuardBusy] = useState(false);

    const [showBl, setShowBl] = useState(false);
    const [showSettings, setShowSettings] = useState(false);
    const [showProfile, setShowProfile] = useState(false);
    const [showRef, setShowRef] = useState(false);
    const [showScroll, setShowScroll] = useState(false);

    const prevOppsRef = useRef({});
    const depthRunnerRef = useRef({ busy: false, queued: null, seq: 0 });
    const spreadLifeRef = useRef({});
    const depthOkCacheRef = useRef({});
    const activeS2SRef = useRef({});
    const [spreadLifeStats, setSpreadLifeStats] = useState({});

    useEffect(() => {
      const handleScroll = () => { if (window.pageYOffset > 300) setShowScroll(true); else setShowScroll(false); };
      window.addEventListener('scroll', handleScroll); return () => window.removeEventListener('scroll', handleScroll);
    }, []);

    const scrollToTop = () => window.scrollTo({ top: 0, behavior: 'smooth' });

    useEffect(() => {
      if (showSettings && userData && userData.isMainAdmin) loadAdminGuardSymbols();
    }, [showSettings, userData && userData.isMainAdmin]);

    useEffect(() => {
      localStorage.setItem('h_lang', lang);
      localStorage.setItem('h_s2f', arbS2F); localStorage.setItem('h_f2f', arbF2F); localStorage.setItem('h_s2s', arbS2S);
      localStorage.setItem('h_sound', isSound);
      localStorage.setItem('h_notif_conv', notifConv); localStorage.setItem('h_notif_div', notifDiv);
      saveScanProfile(arbS2S ? 's2s' : 'main', { minS, maxS, minV, tradeUsd, activeExs, bl });
    }, [lang, minS, maxS, minV, activeExs, bl, arbS2F, arbF2F, arbS2S, isSound, notifConv, notifDiv, tradeUsd]);

    const tgUser = (tg && tg.initDataUnsafe && tg.initDataUnsafe.user) ? tg.initDataUnsafe.user : null;
    const tgId = tgUser ? tgUser.id : null;
    const tgName = tgUser ? (tgUser.username || tgUser.first_name) : 'User';

    useEffect(() => {
      axios.post('/api/auth', {})
        .then(res => {
          setUserData(res.data);
          setTrialUsed(!!res.data.trialUsed);
          try { localStorage.setItem('h_trial_used', res.data.trialUsed ? 'true' : 'false'); } catch(e) {}
          if (res.data.isSubscribed || res.data.isAdmin) setAuthStatus('ok');
          else setAuthStatus('denied');
        })
        .catch(() => setAuthStatus('denied'));
    }, []);

    useEffect(() => {
      if (authStatus !== 'ok') return;
      loadUserAlerts();
      loadAlertBlacklist();
    }, [authStatus]);

    useEffect(() => {
      if (authStatus !== 'ok' || !showNotify) return;
      loadUserAlerts();
      loadAlertBlacklist();
    }, [authStatus, showNotify]);

    useEffect(() => {
      if (authStatus !== 'ok') return;
      const mainProfile = arbS2S ? readScanProfile('main') : { minS, maxS, minV, tradeUsd, activeExs, bl };
      const payload = {
        mainProfile,
        scanModes: { arbS2F, arbF2F, arbS2S },
      };
      const timer = setTimeout(() => {
        axios.post('/api/scan-profile', payload).catch(() => {});
      }, 250);
      return () => clearTimeout(timer);
    }, [authStatus, arbS2F, arbF2F, arbS2S, minS, maxS, minV, tradeUsd, JSON.stringify(activeExs), JSON.stringify(bl)]);

    useEffect(() => {
      if (authStatus !== 'ok') return;

      const updateData = async () => {
        try {
          const r = await axios.get('/api/data');
          if (r.data) setData(r.data);
        } catch (e) {}
      };

      const updateNetworks = async () => {
        try {
          const r = await axios.get('/api/networks');
          if (r.data && r.data.ok) setNetworks(r.data.data || {});
        } catch (e) {}
      };

      updateData();
      updateNetworks();
      const i1 = setInterval(updateData, 5000);
      const i2 = setInterval(updateNetworks, 120000);
      return () => { clearInterval(i1); clearInterval(i2); };
    }, [authStatus]);

    useEffect(() => {
      if (authStatus !== 'ok' || !arbS2S) {
        setServerS2S([]);
        return;
      }
      const pull = async () => {
        try {
          const r = await axios.get('/api/s2s-active');
          const items = r && r.data && Array.isArray(r.data.items) ? r.data.items : [];
          setServerS2S(items);
        } catch (e) {}
      };
      pull();
      const i = setInterval(pull, 6000);
      return () => clearInterval(i);
    }, [authStatus, arbS2S]);

    const activateTrial = () => {
      if (trialUsed) return;
      axios.post('/api/activate-trial', {})
        .then(res => {
          if (res.data.ok) {
            localStorage.setItem('h_trial_used', 'true');
            setTrialUsed(true); setAuthStatus('ok');
            if (tg) tg.showAlert('Пробный доступ активирован!');
          }
        });
    };

    const handlePay = (planName, price) => {
      const text =
        "Привет! Хочу купить подписку: " +
        planName +
        " за $" +
        price +
        ". Мой ID: " +
        (tgId || "");

      axios.post('/api/notify', {
        message: planName + " ($" + price + ")",
        type: "PAYMENT_REQUEST"
      }).catch(() => {});

      const url = "https://t.me/" + window.ADMIN_TG_USERNAME + "?text=" + encodeURIComponent(text);

      if (tg) tg.openTelegramLink(url);
      else window.open(url, "_blank");
    };

    const playSound = () => {
      if (!isSound) return;
      try {
        const ctx = new (window.AudioContext || window.webkitAudioContext)();
        const osc = ctx.createOscillator();
        const gainNode = ctx.createGain();
        osc.type = 'sine';
        osc.frequency.setValueAtTime(880, ctx.currentTime);
        gainNode.gain.setValueAtTime(0.1, ctx.currentTime);
        osc.connect(gainNode);
        gainNode.connect(ctx.destination);
        osc.start();
        osc.stop(ctx.currentTime + 0.2);
      } catch (e) {}
    };

    const toggleMarketType = (type) => {
      setDivAlertForm((prev) => {
        const next = Array.isArray(prev.buyMarketTypes) ? [...prev.buyMarketTypes] : [];
        const idx = next.indexOf(type);
        if (idx >= 0) next.splice(idx, 1);
        else next.push(type);
        return { ...prev, buyMarketTypes: next.length ? next : [type] };
      });
    };

    const toggleExchangeInForm = (field, ex) => {
      setDivAlertForm((prev) => {
        const cur = Array.isArray(prev[field]) ? [...prev[field]] : [];
        const idx = cur.indexOf(ex);
        if (idx >= 0) cur.splice(idx, 1);
        else cur.push(ex);
        return { ...prev, [field]: cur };
      });
    };

    const setAllExchangesInForm = (field) => {
      setDivAlertForm((prev) => ({ ...prev, [field]: [...EXCHANGES] }));
    };

    const loadAdminGuardSymbols = async () => {
      if (!(userData && userData.isMainAdmin)) return;
      try {
        const r = await axios.get('/api/admin/guard-symbols');
        setAdminGuardSymbols(r && r.data && Array.isArray(r.data.items) ? r.data.items : []);
      } catch (e) {}
    };

    const addAdminGuardSymbolByValue = async (rawSymbol, opts = {}) => {
      if (!(userData && userData.isMainAdmin) || adminGuardBusy) return false;
      const symbol = String(rawSymbol || '').toUpperCase().trim().replace(/[^A-Z0-9._-]/g, '');
      if (!symbol) {
        if (!opts.silent && tg) tg.showAlert('Введите тикер');
        return false;
      }
      if (adminGuardSymbols.includes(symbol)) {
        if (!opts.silent && tg) tg.showAlert('Тикер уже в guard-списке');
        return true;
      }
      setAdminGuardBusy(true);
      try {
        const r = await axios.post('/api/admin/guard-symbols', { symbol });
        if (r && r.data && r.data.ok) {
          setAdminGuardSymbols(Array.isArray(r.data.items) ? r.data.items : []);
          if (!opts.silent && tg) tg.showAlert('Тикер добавлен в guard-список');
          return true;
        }
      } catch (e) {
        console.error('guard add failed', e && e.response ? e.response.data : e);
        if (!opts.silent && tg) tg.showAlert('Не удалось добавить тикер');
      } finally {
        setAdminGuardBusy(false);
      }
      return false;
    };

    const addAdminGuardSymbol = async () => {
      const ok = await addAdminGuardSymbolByValue(adminGuardInput);
      if (ok) setAdminGuardInput('');
    };

    const quickAddGuardFromCard = async (symbol) => {
      await addAdminGuardSymbolByValue(symbol);
    };

    const removeAdminGuardSymbol = async (symbol) => {
      if (!(userData && userData.isMainAdmin) || adminGuardBusy) return;
      setAdminGuardBusy(true);
      try {
        const r = await axios.delete('/api/admin/guard-symbols/' + encodeURIComponent(symbol));
        if (r && r.data && r.data.ok) {
          setAdminGuardSymbols(Array.isArray(r.data.items) ? r.data.items : []);
          if (tg) tg.showAlert('Тикер удалён из guard-списка');
        }
      } catch (e) {
        console.error('guard remove failed', e && e.response ? e.response.data : e);
        if (tg) tg.showAlert('Не удалось удалить тикер');
      } finally {
        setAdminGuardBusy(false);
      }
    };

    const loadUserAlerts = async () => {
      try {
        const r = await axios.get('/api/user-alerts');
        setUserAlerts(r && r.data && Array.isArray(r.data.items) ? r.data.items : []);
      } catch (e) {}
    };

    const loadAlertBlacklist = async () => {
      try {
        const r = await axios.get('/api/user-alert-blacklist');
        setAlertBlacklist(r && r.data && Array.isArray(r.data.items) ? r.data.items : []);
      } catch (e) {}
    };

    const addAlertBlacklistCoin = async (coinRaw) => {
      const coin = String(coinRaw || '').trim().toUpperCase();
      if (!coin) { if (tg) tg.showAlert('Введите монету'); return; }
      try {
        const r = await axios.post('/api/user-alert-blacklist', { coin });
        setAlertBlacklist(r && r.data && Array.isArray(r.data.items) ? r.data.items : []);
        setAlertBlacklistInput('');
      } catch (e) {
        if (tg) tg.showAlert('Не удалось добавить монету в ЧС уведомлений');
      }
    };

    const removeAlertBlacklistCoin = async (coin) => {
      try {
        const r = await axios.delete('/api/user-alert-blacklist/' + encodeURIComponent(coin));
        setAlertBlacklist(r && r.data && Array.isArray(r.data.items) ? r.data.items : []);
      } catch (e) {
        if (tg) tg.showAlert('Не удалось удалить монету из ЧС уведомлений');
      }
    };

    const saveConvergenceAlert = async () => {
      if (alertBusy) return;
      const coin = String(convAlertForm.coin || '').trim().toUpperCase();
      if (!coin) { if (tg) tg.showAlert('Введите монету'); return; }
      if (convAlertForm.buyExchange === convAlertForm.sellExchange) { if (tg) tg.showAlert('Биржи покупки и продажи должны отличаться'); return; }
      setAlertBusy(true);
      try {
        const r = await axios.post('/api/user-alerts', {
          coin,
          direction: 'convergence',
          spreadFrom: Number(convAlertForm.spreadFrom),
          spreadTo: convAlertForm.spreadTo === '' ? null : Number(convAlertForm.spreadTo),
          buyExchange: convAlertForm.buyExchange,
          sellExchange: convAlertForm.sellExchange,
          buyMarketType: convAlertForm.buyMarketType,
        });
        if (r && r.data && r.data.ok) {
          setConvAlertForm((p) => ({ ...p, coin: '', spreadFrom: 1, spreadTo: '' }));
          loadUserAlerts();
        }
      } catch (e) {
        if (tg) tg.showAlert('Не удалось сохранить уведомление');
      } finally {
        setAlertBusy(false);
      }
    };

    const saveDivergenceAlert = async () => {
      if (alertBusy) return;
      const buyExchanges = Array.isArray(divAlertForm.buyExchanges) ? divAlertForm.buyExchanges.filter(Boolean) : [];
      const sellExchanges = Array.isArray(divAlertForm.sellExchanges) ? divAlertForm.sellExchanges.filter(Boolean) : [];
      const buyMarketTypes = Array.isArray(divAlertForm.buyMarketTypes) ? divAlertForm.buyMarketTypes.filter(Boolean) : [];
      const coinMode = divAlertForm.coinMode === 'single' ? 'single' : 'all';
      const coin = String(divAlertForm.coin || '').trim().toUpperCase();

      if (!buyExchanges.length || !sellExchanges.length) { if (tg) tg.showAlert('Выбери биржи покупки и продажи'); return; }
      let hasValidPair = false;
      buyExchanges.forEach((bx) => sellExchanges.forEach((sx) => { if (bx !== sx) hasValidPair = true; }));
      if (!hasValidPair) { if (tg) tg.showAlert('Нужна хотя бы одна пара разных бирж'); return; }
      if (!buyMarketTypes.length) { if (tg) tg.showAlert('Выбери Spot и/или Futures для покупки'); return; }
      if (coinMode === 'single' && !coin) { if (tg) tg.showAlert('Введите монету'); return; }

      setAlertBusy(true);
      try {
        const r = await axios.post('/api/user-alerts', {
          direction: 'divergence',
          coinMode,
          coin,
          spreadFrom: Number(divAlertForm.spreadFrom),
          spreadTo: divAlertForm.spreadTo === '' ? null : Number(divAlertForm.spreadTo),
          buyExchanges,
          sellExchanges,
          buyMarketTypes,
        });
        if (r && r.data && r.data.ok) {
          setDivAlertForm((p) => ({ ...p, coin: '', spreadFrom: 1, spreadTo: '' }));
          loadUserAlerts();
        }
      } catch (e) {
        if (tg) tg.showAlert('Не удалось сохранить уведомление');
      } finally {
        setAlertBusy(false);
      }
    };

    const deleteUserAlert = async (id) => {
      try {
        await axios.delete('/api/user-alerts/' + encodeURIComponent(id));
        loadUserAlerts();
      } catch (e) {}
    };


    const formatLifeText = (sec) => {
      const n = Number(sec || 0);
      if (!Number.isFinite(n) || n <= 0) return '0 сек';
      if (n < 60) return Math.round(n) + ' сек';
      const m = Math.floor(n / 60);
      const s = Math.round(n % 60);
      if (m < 60) return s > 0 ? (m + ' мин ' + s + ' сек') : (m + ' мин');
      const h = Math.floor(m / 60);
      const mm = m % 60;
      return mm > 0 ? (h + ' ч ' + mm + ' мин') : (h + ' ч');
    };


    const formatCardPrice = (o, side) => {
      const isS2S = !!(o && o.b && o.s1 && o.b.t === 'S' && o.s1.t === 'S');
      const dc = o && o.depthCheck ? o.depthCheck : null;
      const raw = side === 'buy' ? Number(o && o.b && o.b.p) : Number(o && o.s1 && o.s1.p);
      const exec = side === 'buy'
        ? Number(dc && dc.buyAvgPrice != null ? dc.buyAvgPrice : NaN)
        : Number(dc && dc.sellAvgPrice != null ? dc.sellAvgPrice : NaN);
      const val = isS2S && Number.isFinite(exec) && exec > 0 ? exec : raw;
      if (!Number.isFinite(val) || val <= 0) return '$0';
      return '$' + String(val);
    };

    const copyText = (text) => {
      const s = String(text || '');
      if (!s) { try { if (tg) tg.showAlert('Нет ссылки для копирования'); } catch (e) {} return; }

      const ok = () => { try { if (tg) tg.showAlert('✅ Ссылка скопирована'); } catch (e) {} };
      const fail = () => {
        // fallback: покажем ссылку в prompt, чтобы пользователь мог скопировать вручную
        try { window.prompt('Скопируй ссылку:', s); } catch (e) {}
      };

      try {
        if (navigator.clipboard && navigator.clipboard.writeText) {
          navigator.clipboard.writeText(s).then(ok).catch(() => {
            // старый fallback
            try {
              const el = document.createElement('textarea');
              el.value = s;
              el.setAttribute('readonly', '');
              el.style.position = 'fixed';
              el.style.opacity = '0';
              document.body.appendChild(el);
              el.select();
              document.execCommand('copy');
              document.body.removeChild(el);
              ok();
            } catch (e) { fail(); }
          });
        } else {
          // fallback для старых webview
          try {
            const el = document.createElement('textarea');
            el.value = s;
            el.setAttribute('readonly', '');
            el.style.position = 'fixed';
            el.style.opacity = '0';
            document.body.appendChild(el);
            el.select();
            document.execCommand('copy');
            document.body.removeChild(el);
            ok();
          } catch (e) { fail(); }
        }
      } catch (e) { fail(); }
    };

    const getExchangeMarketUrl = (exchange, symbol, marketType) => {
      const sym = String(symbol || '').toUpperCase().trim();
      const isFut = String(marketType || '') === 'F';
      if (!sym) return '';

      const map = {
        Binance: isFut
          ? 'https://www.binance.com/en/futures/' + sym + 'USDT'
          : 'https://www.binance.com/en/trade/' + sym + '_USDT?type=spot',
        Bybit: isFut
          ? 'https://www.bybit.com/trade/usdt/' + sym + 'USDT'
          : 'https://www.bybit.com/trade/spot/' + sym + '/USDT',
        Gate: isFut
          ? 'https://www.gate.com/futures_trade/USDT/' + sym + '_USDT'
          : 'https://www.gate.com/trade/' + sym + '_USDT',
        MEXC: isFut
          ? 'https://futures.mexc.com/exchange/' + sym + '_USDT?type=linear_swap'
          : 'https://www.mexc.com/exchange/' + sym + '_USDT',
        KuCoin: isFut
          ? 'https://www.kucoin.com/futures/trade/' + sym + 'USDTM'
          : 'https://www.kucoin.com/trade/' + sym + '-USDT',
        Bitget: isFut
          ? 'https://www.bitget.com/futures/usdt/' + sym + 'USDT'
          : 'https://www.bitget.com/spot/' + sym + 'USDT',
        BingX: isFut
          ? 'https://bingx.com/en-us/futures/forward/' + sym + 'USDT/'
          : 'https://bingx.com/en-us/spot/' + sym + 'USDT/',
      };

      return map[exchange] || '';
    };

    const openExternalLink = (url) => {
      if (!url) return;

      try {
        if (tg && typeof tg.openLink === 'function') {
          tg.openLink(url, { try_instant_view: false });
          return;
        }
      } catch (e) {}

      try {
        window.open(url, '_blank', 'noopener,noreferrer');
      } catch (e) {
        try { window.location.href = url; } catch (_) {}
      }
    };

    const openGuide = () => {
      openExternalLink(window.GUIDE_URL || '/guide.pdf');
    };

    const openExchangeMarket = (exchange, symbol, marketType) => {
      const url = getExchangeMarketUrl(exchange, symbol, marketType);
      if (!url) return;
      openExternalLink(url);
    };


    // Spot -> Spot: только реальные маршруты перевода между биржами
    const getSpotSpotRoutes = (coin, buyEx, sellEx) => {
      const c = String(coin || '').toUpperCase().trim();
      if (!c || !buyEx || !sellEx) return [];

      const buyCoin = networks && networks[buyEx] && networks[buyEx][c];
      const sellCoin = networks && networks[sellEx] && networks[sellEx][c];
      const buyChains = buyCoin && buyCoin.chains ? buyCoin.chains : {};
      const sellChains = sellCoin && sellCoin.chains ? sellCoin.chains : {};

      const routes = [];

      Object.keys(buyChains).forEach((chain) => {
        const fromNet = buyChains[chain];
        const toNet = sellChains[chain];

        if (!fromNet || !toNet) return;
        if (!fromNet.withdraw) return;
        if (!toNet.deposit) return;

        const feeCoin = fromNet.fee != null ? Number(fromNet.fee) : null;
        if (feeCoin == null || !Number.isFinite(feeCoin) || feeCoin < 0) return;

        routes.push({
          chain,
          fee: feeCoin,
          minWithdraw: fromNet.minWithdraw != null ? Number(fromNet.minWithdraw) : null,
          minDeposit: toNet.minDeposit != null ? Number(toNet.minDeposit) : null,
          etaMin: fromNet.etaMin != null ? Number(fromNet.etaMin) : (toNet.etaMin != null ? Number(toNet.etaMin) : null),
          feeKnown: true,
        });
      });

      routes.sort((a, b) => {
        const feeA = a.fee == null ? Number.POSITIVE_INFINITY : a.fee;
        const feeB = b.fee == null ? Number.POSITIVE_INFINITY : b.fee;
        if (feeA !== feeB) return feeA - feeB;

        const etaA = a.etaMin == null ? Number.POSITIVE_INFINITY : a.etaMin;
        const etaB = b.etaMin == null ? Number.POSITIVE_INFINITY : b.etaMin;
        return etaA - etaB;
      });

      return routes;
    };

    const buildConfirmedS2S = (base, check) => {
      if (!base || !check || !check.ok) return null;
      const grossSp = Number(base.sp || 0);
      const execGrossSp = Number(check && check.grossExecutablePct != null ? check.grossExecutablePct : grossSp);
      const netSp = Number(check.realizedPct);
      const displaySp = Number.isFinite(execGrossSp) ? execGrossSp : (Number.isFinite(grossSp) ? grossSp : 0);
      return {
        ...base,
        routes: (check.routes && check.routes.length > 0) ? check.routes : (base.routes || []),
        displaySp: displaySp,
        displaySpText: Number(displaySp).toFixed(2),
        netSp: Number.isFinite(netSp) ? netSp : null,
        netSpText: Number.isFinite(netSp) ? Number(netSp).toFixed(2) : null,
        depthCheck: check,
        _confirmedAt: Date.now(),
      };
    };

    const rebuildVisibleS2S = (mapOverride) => {
      try {
        const srcMap = mapOverride && typeof mapOverride === 'object' ? mapOverride : (activeS2SRef.current || {});
        const nextMap = { ...srcMap };
        const nextList = Object.values(nextMap).filter((o) => {
          if (!o || !o.id || !o.b || !o.s1) return false;
          if (!(o.b.t === 'S' && o.s1.t === 'S')) return false;
          if (bl.indexOf(o.s) !== -1) return false;
          if (activeExs.indexOf(o.b.ex) === -1 || activeExs.indexOf(o.s1.ex) === -1) return false;
          return true;
        });
        nextList.sort((a, b) => Number(b.displaySp != null ? b.displaySp : b.sp) - Number(a.displaySp != null ? a.displaySp : a.sp));
        activeS2SRef.current = nextMap;
        setActiveS2S(nextMap);
        setVisibleS2S(nextList);
        try {
          window.__S2S_VISIBLE_COUNT__ = nextList.length;
          window.__S2S_VISIBLE_IDS__ = nextList.slice(0, 20).map((o) => o.id);
          console.log('[S2S][UI][VISIBLE] count=' + nextList.length + ' ids=' + JSON.stringify(window.__S2S_VISIBLE_IDS__));
        } catch (e) {}
      } catch (e) {}
    };

    useEffect(() => {
      rebuildVisibleS2S();
    }, [activeExs, bl]);

    const guardSymbols = useMemo(() => {
      const meta = data && data.__meta && Array.isArray(data.__meta.guardSymbols) ? data.__meta.guardSymbols : [];
      return meta.map((x) => String(x || '').toUpperCase()).filter(Boolean);
    }, [data]);

    const maxReasonableSpreadPct = useMemo(() => {
      const n = Number(data && data.__meta && data.__meta.maxReasonableSpreadPct);
      return Number.isFinite(n) && n > 0 ? n : 1000;
    }, [data]);

    const maxSpotAnchorRatio = useMemo(() => {
      const n = Number(data && data.__meta && data.__meta.maxSpotAnchorRatio);
      return Number.isFinite(n) && n > 1 ? n : 3;
    }, [data]);

    const maxGuardAnchorRatio = useMemo(() => {
      const n = Number(data && data.__meta && data.__meta.maxGuardAnchorRatio);
      return Number.isFinite(n) && n > 1 ? n : 2;
    }, [data]);

    const shouldRejectGuardedSymbol = (sym, buy, sell) => {
      const coin = String(sym || '').toUpperCase();
      // Guard-список работает как жёсткое скрытие тикера из выдачи.
      if (coin && guardSymbols.indexOf(coin) !== -1) return true;
      return false;
    };

    const rawOpps = useMemo(() => {
      if (!data) return [];

      const m = {};
      const res = [];

      EXCHANGES.forEach((ex) =>
        (data[ex] || []).forEach((c) => {
          if (bl.indexOf(c.s) !== -1) return;
          if (!m[c.s]) m[c.s] = [];
          m[c.s].push({ ex: ex, p: c.p, v: c.v, t: c.t, f: (c.f != null ? c.f : null) });
        })
      );

      Object.keys(m).forEach((sym) => {
        const k = m[sym];
        if (!k || k.length < 2) return;

        for (let i = 0; i < k.length; i++) {
          for (let j = 0; j < k.length; j++) {
            const b = k[i];
            const s1 = k[j];

            if (b.ex === s1.ex) continue;

            const isS2S = b.t === 'S' && s1.t === 'S';
            const volumeThreshold = isS2S ? Math.max(Number(minV) || 0, S2S_MIN_VOLUME_USDT) : (Number(minV) || 0);
            if (b.v < volumeThreshold || s1.v < volumeThreshold) continue;


            // ✅ futures sanity: drop phantom contracts (no funding)
            if (b.t === 'F' && (b.f == null || b.f === 0)) continue;
            if (s1.t === 'F' && (s1.f == null || s1.f === 0)) continue;

            let validMode = false;
            if (arbS2F && b.t === 'S' && s1.t === 'F') validMode = true;
            if (arbF2F && b.t === 'F' && s1.t === 'F') validMode = true;
            if (arbS2S && b.t === 'S' && s1.t === 'S') validMode = true;
            if (!validMode) continue;

            if (activeExs.indexOf(b.ex) === -1 || activeExs.indexOf(s1.ex) === -1) continue;

            let routesForS2S = null;

if (b.t === 'S' && s1.t === 'S') {
  routesForS2S = getSpotSpotRoutes(sym, b.ex, s1.ex);

  // Для spot->spot показываем только полностью подтверждённые маршруты:
  // есть общая сеть, вывод открыт, ввод открыт и комиссия сети известна.
  const hasKnownRoute = Array.isArray(routesForS2S)
    && routesForS2S.some((route) => route && route.fee != null && Number.isFinite(Number(route.fee)));
  if (!hasKnownRoute) {
    continue;
  }

  // Не отсекаем связку по первой попавшейся сети.
  // Если tradeUsd позволяет пройти хотя бы одну общую сеть по minWithdraw,
  // оставляем пару для дальнейшего server depth-check.
  if (Number(tradeUsd) > 0 && Number(b.p) > 0) {
    const coinAmount = Number(tradeUsd) / Number(b.p);
    const hasEligibleRoute = routesForS2S.some((route) => {
      if (!route || route.fee == null || !Number.isFinite(Number(route.fee))) return false;
      if (route.minWithdraw == null) return true;
      return coinAmount >= Number(route.minWithdraw);
    });
    if (!hasEligibleRoute) {
      continue;
    }
  }
}

            const sp = ((s1.p - b.p) / b.p) * 100;
            if (!Number.isFinite(sp) || Math.abs(sp) > maxReasonableSpreadPct) continue;
            if (shouldRejectGuardedSymbol(sym, b, s1)) continue;

            // Для spot->spot отправляем на depth-check оба направления,
            // даже если теоретический спред по lastPrice отрицательный.
            // Финальное решение всё равно принимает сервер по реальному исполнению,
            // доступности ввода/вывода и прибыли после комиссии перевода.
            if (!(b.t === 'S' && s1.t === 'S')) {
              if (sp < minS || sp > maxS) continue;
            }

            res.push({
              id: sym + "_" + b.ex + "_" + s1.ex + "_" + b.t + "_" + s1.t,
              s: sym,
              sp: sp.toFixed(2),
              absSp: Math.abs(sp),
              b: b,
              s1: s1,
              routes: routesForS2S,
            });
          }
        }
      });

      return res.sort((a, b) => Number(b.absSp != null ? b.absSp : b.sp) - Number(a.absSp != null ? a.absSp : a.sp));
    }, [data, minS, maxS, minV, tradeUsd, activeExs, bl, arbS2F, arbF2F, arbS2S, networks, guardSymbols, maxReasonableSpreadPct, maxSpotAnchorRatio]);

    useEffect(() => {
      if (authStatus !== 'ok' || !arbS2S) {
        depthRunnerRef.current.queued = null;
        return;
      }

      const s2sCandidates = rawOpps
        .filter((o) => o && o.b && o.s1 && o.b.t === 'S' && o.s1.t === 'S' && o.routes && o.routes.length > 0);

      const byRoute = {};
      s2sCandidates.forEach((o) => {
        const key = String(o.b.ex) + '->' + String(o.s1.ex);
        if (!byRoute[key]) byRoute[key] = [];
        byRoute[key].push(o);
      });

      Object.keys(byRoute).forEach((key) => {
        byRoute[key].sort((a, b) => Number(b.absSp != null ? b.absSp : b.sp) - Number(a.absSp != null ? a.absSp : a.sp));
      });

      const routeKeys = Object.keys(byRoute).sort();
      const balanced = [];
      let added = true;
      let round = 0;
      const MAX_S2S_PAIRS = 500;

      while (added && balanced.length < MAX_S2S_PAIRS) {
        added = false;
        for (const key of routeKeys) {
          const arr = byRoute[key] || [];
          if (arr[round]) {
            balanced.push(arr[round]);
            added = true;
            if (balanced.length >= MAX_S2S_PAIRS) break;
          }
        }
        round += 1;
      }

      const s2sPairs = balanced.map((o) => ({
        id: o.id,
        sym: o.s,
        buyEx: o.b.ex,
        sellEx: o.s1.ex,
        buyPrice: o.b.p,
        sellPrice: o.s1.p,
        buyVol: o.b.v,
        sellVol: o.s1.v,
        buyType: o.b.t,
        sellType: o.s1.t,
        routes: o.routes,
        routeFeeCoin: o.routes && o.routes[0] && o.routes[0].fee != null ? Number(o.routes[0].fee) : null,
        minDepositCoin: o.routes && o.routes[0] && o.routes[0].minDeposit != null ? o.routes[0].minDeposit : null,
      }));
      const s2sSourceById = {};
      balanced.forEach((o) => { if (o && o.id) s2sSourceById[o.id] = o; });

      try {
        const bySym = {};
        s2sPairs.forEach((p) => {
          if (!p || !p.sym) return;
          if (!bySym[p.sym]) bySym[p.sym] = [];
          bySym[p.sym].push(String(p.buyEx) + '->' + String(p.sellEx));
        });
        const samples = Object.keys(bySym).slice(0, 8).map((sym) => {
          const dirs = Array.from(new Set(bySym[sym])).sort();
          return sym + ': ' + dirs.join(', ');
        });
        console.log('[S2S][UI] raw=' + s2sCandidates.length + ' balanced=' + s2sPairs.length + ' samples=' + JSON.stringify(samples));
      } catch (e) {}

      if (!s2sPairs.length) {
        depthRunnerRef.current.queued = null;
        return;
      }

      const nextSeq = (depthRunnerRef.current.seq || 0) + 1;
      depthRunnerRef.current.seq = nextSeq;
      depthRunnerRef.current.queued = {
        seq: nextSeq,
        tradeUsd: Number(tradeUsd),
        minPct: Number(minS),
        maxPct: Number(maxS),
        pairs: s2sPairs,
        sourceById: s2sSourceById,
      };

      const pump = async () => {
        const runner = depthRunnerRef.current;
        if (runner.busy) return;
        runner.busy = true;

        while (runner.queued) {
          const job = runner.queued;
          runner.queued = null;
          const merged = {};
          const chunkSize = 120;

          for (let i = 0; i < job.pairs.length; i += chunkSize) {
            const chunk = job.pairs.slice(i, i + chunkSize);
            try {
              const r = await axios.post('/api/spot-depth-check', {
                tradeUsd: job.tradeUsd,
                minPct: job.minPct,
                maxPct: job.maxPct,
                pairs: chunk
              });
              const checks = r && r.data && r.data.checks ? r.data.checks : {};
              Object.assign(merged, checks);
            } catch (e) {
              chunk.forEach((pair) => {
                if (pair && pair.id) merged[pair.id] = { ok: false, reason: 'depth_batch_error' };
              });
            }
          }

          if (!runner.queued || runner.queued.seq <= job.seq) {
            try {
              const nowTs = Date.now();
              const cache = depthOkCacheRef.current || {};
              Object.keys(merged).forEach((id) => {
                const check = merged[id];
                if (check && check.ok) cache[id] = { ...check, _ts: nowTs };
              });
              const TTL_MS = 20000;
              Object.keys(cache).forEach((id) => {
                const item = cache[id];
                if (!item || (nowTs - Number(item._ts || 0)) > TTL_MS) delete cache[id];
              });
              depthOkCacheRef.current = cache;
              const nextChecks = { ...merged };
              Object.keys(cache).forEach((id) => {
                if (!nextChecks[id] || !nextChecks[id].ok) {
                  nextChecks[id] = { ...cache[id], reason: 'ok_recent' };
                }
              });
              setSpotDepthChecks(nextChecks);

              const activeMap = activeS2SRef.current || {};
              const nowTs2 = Date.now();
              Object.keys(job.sourceById || {}).forEach((id) => {
                const base = job.sourceById[id];
                const check = nextChecks[id];
                if (check && check.ok) {
                  const built = buildConfirmedS2S(base, check);
                  if (built) {
                    const prev = activeMap[id] || {};
                    activeMap[id] = { ...prev, ...built, _misses: 0, _lastOkTs: nowTs2 };
                  }
                } else if (activeMap[id]) {
                  activeMap[id]._misses = Number(activeMap[id]._misses || 0) + 1;
                  if (activeMap[id]._misses >= 5) delete activeMap[id];
                }
              });
              Object.keys(activeMap).forEach((id) => {
                const item = activeMap[id];
                if (!item) return;
                if ((nowTs2 - Number(item._lastOkTs || 0)) > 45000) delete activeMap[id];
              });
              activeS2SRef.current = activeMap;
              rebuildVisibleS2S(activeMap);
            } catch (e) {
              setSpotDepthChecks(merged);
            }
          }
        }

        runner.busy = false;
      };

      pump();
    }, [authStatus, arbS2S, rawOpps, tradeUsd, minS, maxS]);

    useEffect(() => {
      if (authStatus !== 'ok' || !arbS2S) return;
      const tick = async () => {
        const entries = Object.values(activeS2SRef.current || {});
        if (!entries.length) return;
        const pairs = entries.map((o) => ({
          id: o.id,
          sym: o.s,
          buyEx: o.b.ex,
          sellEx: o.s1.ex,
          buyPrice: o.b.p,
          sellPrice: o.s1.p,
          routes: o.routes || [],
          routeFeeCoin: o.routes && o.routes[0] && o.routes[0].fee != null ? Number(o.routes[0].fee) : null,
          minDepositCoin: o.routes && o.routes[0] && o.routes[0].minDeposit != null ? o.routes[0].minDeposit : null,
        }));
        const merged = {};
        const chunkSize = 80;
        for (let i = 0; i < pairs.length; i += chunkSize) {
          const chunk = pairs.slice(i, i + chunkSize);
          try {
            const r = await axios.post('/api/spot-depth-check', { tradeUsd: Number(tradeUsd), minPct: Number(minS), maxPct: Number(maxS), pairs: chunk });
            const checks = r && r.data && r.data.checks ? r.data.checks : {};
            Object.assign(merged, checks);
          } catch (e) {}
        }
        const activeMap = activeS2SRef.current || {};
        const nowTs = Date.now();
        entries.forEach((base) => {
          const check = merged[base.id];
          if (check && check.ok) {
            const built = buildConfirmedS2S(base, check);
            if (built) activeMap[base.id] = { ...activeMap[base.id], ...built, _misses: 0, _lastOkTs: nowTs };
          } else if (activeMap[base.id]) {
            activeMap[base.id]._misses = Number(activeMap[base.id]._misses || 0) + 1;
            if (activeMap[base.id]._misses >= 3) delete activeMap[base.id];
          }
        });
        Object.keys(activeMap).forEach((id) => {
          const item = activeMap[id];
          if (!item) return;
          if ((nowTs - Number(item._lastOkTs || 0)) > 45000) delete activeMap[id];
        });
        activeS2SRef.current = activeMap;
        rebuildVisibleS2S(activeMap);
      };
      const i = setInterval(tick, 6000);
      return () => clearInterval(i);
    }, [authStatus, arbS2S, tradeUsd, minS, maxS]);

    const opps = useMemo(() => {
      const list = [];

      // НЕ-SPOT->SPOT рендерим как раньше из сырого списка.
      (rawOpps || []).forEach((o) => {
        if (!o || !o.b || !o.s1) return;
        const isS2S = o.b.t === 'S' && o.s1.t === 'S';
        if (isS2S) return;
        list.push({ ...o, displaySp: Number(o.sp) });
      });

      // SPOT->SPOT рендерим ТОЛЬКО из серверного подтверждённого списка.
      // Это убирает рассинхрон между серверным ok и фронтовыми состояниями.
      if (arbS2S && Array.isArray(serverS2S) && serverS2S.length) {
        serverS2S.forEach((o) => {
          if (!o || !o.id || !o.b || !o.s1) return;
          if (!(o.b.t === 'S' && o.s1.t === 'S')) return;
          if (bl.indexOf(o.s) !== -1) return;
          if (activeExs.indexOf(o.b.ex) === -1 || activeExs.indexOf(o.s1.ex) === -1) return;

          const check = spotDepthChecks && spotDepthChecks[o.id] ? spotDepthChecks[o.id] : (o.depthCheck || null);
          const grossSp = Number(o.sp != null ? o.sp : (o.displaySp != null ? o.displaySp : 0));
          const netSp = Number(check && check.realizedPct != null ? check.realizedPct : (o.netSp != null ? o.netSp : null));
          const displaySp = Number.isFinite(grossSp) ? grossSp : 0;

          list.push({
            ...o,
            routes: (check && check.routes && check.routes.length > 0) ? check.routes : (o.routes || []),
            displaySp,
            displaySpText: Number(displaySp).toFixed(2),
            netSp: Number.isFinite(netSp) ? netSp : null,
            netSpText: Number.isFinite(netSp) ? Number(netSp).toFixed(2) : null,
            depthCheck: check || o.depthCheck || null,
          });
        });
      }

      list.sort((a, b) => Number(b.displaySp != null ? b.displaySp : b.sp) - Number(a.displaySp != null ? a.displaySp : a.sp));
      try {
        const s2sVisible = list.filter((o) => o && o.b && o.s1 && o.b.t === 'S' && o.s1.t === 'S');
        window.__S2S_VISIBLE_COUNT__ = s2sVisible.length;
        window.__S2S_VISIBLE_IDS__ = s2sVisible.slice(0, 20).map((o) => o.id);
        console.log('[S2S][UI][VISIBLE] count=' + s2sVisible.length + ' ids=' + JSON.stringify(window.__S2S_VISIBLE_IDS__));
      } catch (e) {}
      return list;
    }, [rawOpps, serverS2S, spotDepthChecks, arbS2S, activeExs, bl]);

    useEffect(() => {
      const now = Date.now();
      const store = spreadLifeRef.current || {};
      const seen = {};

      opps.forEach((o) => {
        if (!o || !o.id) return;
        seen[o.id] = true;
        let item = store[o.id];
        if (!item) {
          item = { activeSince: now, lastSeenAt: now, totalCompletedMs: 0, completedCount: 0, lastTouchedAt: now };
          store[o.id] = item;
        }
        if (!item.activeSince) item.activeSince = now;
        item.lastSeenAt = now;
        item.lastTouchedAt = now;
      });

      Object.keys(store).forEach((id) => {
        const item = store[id];
        if (!seen[id] && item.activeSince) {
          const endAt = item.lastSeenAt || now;
          const dur = Math.max(0, endAt - item.activeSince);
          item.totalCompletedMs += dur;
          item.completedCount += 1;
          item.activeSince = null;
        }
        if (!seen[id] && (now - (item.lastTouchedAt || 0)) > 30 * 60 * 1000) {
          delete store[id];
        }
      });

      const nextStats = {};
      opps.forEach((o) => {
        const item = store[o.id];
        if (!item) return;
        const currentMs = item.activeSince ? Math.max(0, now - item.activeSince) : 0;
        const avgMs = item.completedCount > 0 ? (item.totalCompletedMs / item.completedCount) : currentMs;
        nextStats[o.id] = {
          currentSec: Math.max(0, Math.round(currentMs / 1000)),
          avgSec: Math.max(0, Math.round(avgMs / 1000)),
        };
      });

      spreadLifeRef.current = store;
      setSpreadLifeStats(nextStats);
    }, [opps]);

    useEffect(() => {
      const sorted = opps;
      let shouldAlert = false;
      const currentIds = {};

      sorted.forEach((o) => {
        const curSp = Number(o.displaySp != null ? o.displaySp : o.sp);
        currentIds[o.id] = curSp;
        const oldSp = prevOppsRef.current[o.id];
        if (!oldSp && notifDiv) shouldAlert = true;
        if (oldSp && Math.abs(curSp - Number(oldSp)) >= 1.0 && notifDiv) shouldAlert = true;
      });

      if (notifConv) {
        Object.keys(prevOppsRef.current).forEach((id) => {
          if (!currentIds[id]) shouldAlert = true;
        });
      }

      if (shouldAlert) playSound();
      prevOppsRef.current = currentIds;
    }, [opps, notifConv, notifDiv, isSound]);

    const PriceModal = () => (
      <div className="modal" style={{zIndex: 20000, maxHeight:'86vh', overflowY:'auto', padding:'22px 18px 26px', borderRadius:'26px', border:'1px solid rgba(0,212,255,0.10)', background:'linear-gradient(180deg, rgba(9,15,22,0.985) 0%, rgba(6,10,15,0.985) 100%)', boxShadow:'0 18px 40px rgba(0,0,0,0.38)'}}>
        <div style={{display:'flex', justifyContent:'space-between', alignItems:'flex-start', gap:'14px', marginBottom:'18px', flexWrap:'wrap'}}>
          <div style={{minWidth:0, flex:'1 1 260px'}}>
            <div style={{display:'flex', alignItems:'center', gap:'10px', flexWrap:'wrap'}}>
              <h2 style={{color:'#00d4ff', margin:0, fontSize:'24px', fontWeight:'900', letterSpacing:'0.02em'}}>{t.tariffs} HEDGE PRO</h2>
              <div style={{padding:'6px 10px', borderRadius:'999px', border:'1px solid rgba(0,212,255,0.18)', background:'rgba(0,212,255,0.07)', color:'#d7f8ff', fontSize:'11px', fontWeight:'800'}}>доступ активируется вручную</div>
            </div>
            <div style={{color:'#7f909b', fontSize:'12px', lineHeight:'1.55', marginTop:'8px'}}>Выберите подходящий срок доступа. Карточки ниже оформлены компактно, но логика оплаты и выдачи доступа не меняется.</div>
          </div>
          <button onClick={() => setShowPrices(false)} style={{width:'44px', height:'44px', borderRadius:'14px', border:'1px solid rgba(255,255,255,0.08)', background:'rgba(255,255,255,0.035)', color:'#fff', fontSize:'28px', lineHeight:1, cursor:'pointer', flexShrink:0}}>✕</button>
        </div>

        <div style={{display:'grid', gap:'12px'}}>
          {[
            {name: '1 Месяц', price: 100, desc: 'Базовый доступ к сканеру', badge: '', accent:'#4dd8ff'},
            {name: '3 Месяца', price: 280, desc: 'VIP чат', badge: 'Выгода 6%', accent:'#65ff9a'},
            {name: '6 Месяцев', price: 570, desc: 'Приоритетная поддержка', badge: 'Выгода 15%', accent:'#ffd76a'},
            {name: '1 Год', price: 1100, desc: 'Максимальная выгода', badge: 'Выгода 30%', accent:'#ff9f6a'}
          ].map(plan => (
            <button
              key={plan.name}
              className="price-card"
              onClick={() => handlePay(plan.name, plan.price)}
              style={{display:'block', width:'100%', textAlign:'left', padding:'18px', borderRadius:'22px', border:'1px solid rgba(255,255,255,0.06)', background:'linear-gradient(180deg, rgba(9,15,22,0.98) 0%, rgba(7,11,16,0.98) 100%)', boxShadow:'0 12px 24px rgba(0,0,0,0.18)'}}
            >
              <div style={{display:'flex', justifyContent:'space-between', alignItems:'flex-start', gap:'14px', width:'100%', flexWrap:'wrap'}}>
                <div style={{minWidth:0, flex:'1 1 220px'}}>
                  <div style={{display:'flex', alignItems:'center', gap:'8px', flexWrap:'wrap'}}>
                    <div style={{fontSize:'18px', fontWeight:'900', color:'#fff'}}>{plan.name}</div>
                    {plan.badge && <span style={{fontSize:'11px', background:'rgba(255,255,255,0.06)', color:plan.accent, padding:'4px 8px', borderRadius:'999px', border:'1px solid rgba(255,255,255,0.08)', fontWeight:'800'}}>{plan.badge}</span>}
                  </div>
                  <div style={{fontSize:'12px', color:'#7f909b', marginTop:'6px', lineHeight:'1.5'}}>{plan.desc}</div>
                </div>
                <div style={{minWidth:'110px', padding:'12px 14px', borderRadius:'18px', background:'rgba(0,212,255,0.06)', border:'1px solid rgba(0,212,255,0.14)', textAlign:'right'}}>
                  <div style={{fontSize:'10px', textTransform:'uppercase', color:'#7fa7b3', letterSpacing:'0.05em'}}>Стоимость</div>
                  <div className="price-val" style={{marginTop:'4px', color:'#fff', fontWeight:'900', fontSize:'26px'}}>{'$' + plan.price}</div>
                </div>
              </div>
            </button>
          ))}
        </div>

        <button onClick={() => setShowPrices(false)} style={{width:'100%', padding:'15px', background:'linear-gradient(180deg, #11151a 0%, #0d1014 100%)', color:'#fff', border:'1px solid rgba(255,255,255,0.08)', borderRadius:'16px', marginTop:'18px', fontWeight:'900'}}>{t.close}</button>
      </div>
    );


    if (authStatus === 'loading') return <div id="splash"><MatrixIntro /><h1 style={{color:'#00d4ff', zIndex:1, letterSpacing:'4px', fontSize:'22px', fontWeight:'900', position:'absolute'}}>{t.loading}</h1></div>;

    if (authStatus === 'denied') return (
      <div id="splash">
        <MatrixIntro />
        <div style={{zIndex: 1, position: 'relative', width: '90%'}}>
          <h1 style={{color:'#ff3b3b', textAlign:'center', margin:'0 0 10px 0'}}>{t.access_denied}</h1>
          <p style={{color:'#666', marginBottom:'30px', fontSize:'14px'}}>{t.sub_required}</p>
          {!trialUsed && <button className="btn-trial" onClick={activateTrial}>{t.trial_btn}</button>}
          <button className="btn-pay" onClick={() => setShowPrices(true)}>{t.pay_btn}</button>
        </div>
        {showPrices && <PriceModal />}
      </div>
    );

    if (!data) return <div id="splash"><MatrixIntro /><h1 style={{color:'#00d4ff', zIndex:1, letterSpacing:'4px', fontSize:'22px', fontWeight:'900', position:'absolute'}}>{t.waiting}</h1></div>;

    return (
      <div>
        <button className="fab-notify" onClick={() => setShowNotify(true)}>🔔</button>

        <header className="header-main" style={{border:'1px solid rgba(0,212,255,0.10)', borderRadius:'20px', background:'linear-gradient(180deg, rgba(10,18,28,0.98) 0%, rgba(8,13,20,0.98) 100%)', boxShadow:'0 14px 26px rgba(0,0,0,0.22)', padding:'10px 10px 10px'}}>
          <div style={{display:'flex', justifyContent:'space-between', alignItems:'center', gap:'8px', marginBottom:'10px'}}>
            <div style={{display:'flex', gap:'8px', alignItems:'center'}}>
              <button onClick={() => setShowSettings(true)} style={{background:'linear-gradient(180deg, #151c24 0%, #10161d 100%)', border:'1px solid rgba(255,255,255,0.08)', padding:'8px 10px', borderRadius:'12px', fontSize:'15px', boxShadow:'inset 0 1px 0 rgba(255,255,255,0.04)'}}>⚙️</button>
              <button onClick={() => setShowProfile(true)} style={{background:'linear-gradient(180deg, rgba(0,212,255,0.10) 0%, rgba(0,212,255,0.04) 100%)', border:'1px solid rgba(0,212,255,0.24)', color:'#00d4ff', padding:'8px 12px', borderRadius:'12px', fontWeight:'900', fontSize:'10px', letterSpacing:'0.02em', boxShadow:'0 8px 16px rgba(0,212,255,0.07)'}}>👤 {t.profile}</button>
            </div>
            <div style={{display:'flex', alignItems:'center', justifyContent:'center', minWidth:0, flex:'1 1 auto'}}>
              <div style={{fontSize:'11px', color:'#90a2ae', textTransform:'uppercase', letterSpacing:'0.14em', fontWeight:'800', textAlign:'center', whiteSpace:'nowrap'}}>HEDGE TERMINAL PRO</div>
            </div>
            <button onClick={() => setShowBl(true)} style={{background:'linear-gradient(180deg, rgba(58,9,14,0.95) 0%, rgba(32,7,11,0.98) 100%)', border:'1px solid rgba(255,59,59,0.22)', color:'#ff5a5a', padding:'8px 12px', borderRadius:'12px', fontWeight:'900', fontSize:'10px', letterSpacing:'0.02em'}}>{t.bl}</button>
          </div>

          <div style={{display:'flex', justifyContent:'center', gap:'6px', marginBottom:'10px', flexWrap:'wrap'}}>
            {EXCHANGES.map(ex => (
              <button
                key={ex}
                onClick={() => setActiveExs(p => p.includes(ex) ? p.filter(x => x !== ex) : [...p, ex])}
                className={"btn-ex " + (activeExs.includes(ex) ? "active-ex" : "")}
              >{ex}</button>
            ))}
          </div>

          <div className="input-grid" style={{gap:'8px'}}>
            <div className="input-wrap"><label>{t.min_p}</label><input type="number" value={minS} onChange={e => setMinS(Number(e.target.value))} /></div>
            <div className="input-wrap"><label>{t.max_p}</label><input type="number" value={maxS} onChange={e => setMaxS(Number(e.target.value))} /></div>
            <div className="input-wrap"><label>{t.vol}</label><input type="number" value={minV} onChange={e => setMinV(Number(e.target.value))} /></div>
            <div className="input-wrap"><label>Сделка $</label><input type="number" value={tradeUsd} onChange={e => setTradeUsd(Number(e.target.value))} /></div>
          </div>
        </header>

        {opps.length > 0 ? opps.map((o, i) => (
          <div key={i} className="card" style={{position:'relative', overflow:'hidden', border:'1px solid rgba(255,255,255,0.06)', borderRadius:'20px', background:'linear-gradient(180deg, rgba(10,15,22,0.98) 0%, rgba(7,11,16,0.98) 100%)', boxShadow:'0 14px 28px rgba(0,0,0,0.22), inset 0 1px 0 rgba(255,255,255,0.03)', padding:'12px'}}>
            <div style={{position:'absolute', top:'14px', right:'14px', display:'flex', flexDirection:'column', gap:'8px', zIndex:10}}>
              <button className="close-btn" style={{position:'static'}} onClick={() => setBl([...bl, o.s])} title="Добавить в ЧС">✕</button>
              {userData && userData.isMainAdmin && (
                <button
                  type="button"
                  onClick={() => quickAddGuardFromCard(o.s)}
                  title="Добавить тикер в guard-список"
                  style={{background:'#1b1b08', color:'#ffeb3b', border:'1px solid #4a4300', width:'28px', height:'28px', borderRadius:'50%', cursor:'pointer', display:'flex', alignItems:'center', justifyContent:'center', fontWeight:'900', fontSize:'13px'}}
                >🛡</button>
              )}
            </div>

            <div style={{display:'flex', justifyContent:'space-between', alignItems:'flex-start', gap:'10px', marginBottom:'10px', paddingRight:'40px'}}>
              <div style={{minWidth:0}}>
                <div style={{fontSize:'20px', fontWeight:'900', color:'#fff', lineHeight:1.05, letterSpacing:'0.01em'}}>{o.s}</div>
                <div style={{marginTop:'4px', fontSize:'9px', textTransform:'uppercase', letterSpacing:'0.08em', color:'#6f8793', fontWeight:'700'}}>Арбитражная связка</div>
              </div>
              <div style={{textAlign:'right', padding:'8px 10px', borderRadius:'14px', background:'linear-gradient(180deg, rgba(0,255,100,0.10) 0%, rgba(0,255,100,0.04) 100%)', border:'1px solid rgba(0,255,100,0.18)', minWidth:'90px'}}>
                <div style={{fontSize:'9px', color:'#85b89a', textTransform:'uppercase', letterSpacing:'0.06em'}}>Спред</div>
                <div style={{color:'#00ff64', fontSize:'20px', fontWeight:'900', marginTop:'3px', lineHeight:1}}>{(o.displaySpText || o.sp)}%</div>
              </div>
            </div>

            <div style={{display:'grid', gridTemplateColumns:'1fr 1fr', gap:'10px', marginTop:'10px'}}>
              <div style={{background:'linear-gradient(180deg, rgba(16,24,34,0.92) 0%, rgba(11,16,24,0.96) 100%)', padding:'11px', borderRadius:'16px', border:'1px solid rgba(255,255,255,0.07)', boxShadow:'inset 0 1px 0 rgba(255,255,255,0.03)'}}>
                <div style={{fontSize:'12px', color:'#54dfff', fontWeight:'900', letterSpacing:'0.01em'}}>
                  {o.b.ex} <span className="badge" style={{background: o.b.t === 'S' ? '#3e8e41' : '#222'}}>{o.b.t === 'S' ? 'SPOT' : 'FUT'}</span>
                </div>
                <div style={{fontSize:'16px', margin:'6px 0 5px', fontWeight:'900', color:'#fff'}}>{formatCardPrice(o, 'buy')}</div>
                <div style={{fontSize:'9px', color:'#7f909b'}}>Vol: {Math.round(o.b.v)}</div>
                {o.b.t === 'F' && o.b.f != null && (<div style={{fontSize:'9px', color:'#ffeb3b'}}>Funding: {(o.b.f*100).toFixed(4)}%</div>)}
                <button type="button" className="btn-buy" onClick={() => openExchangeMarket(o.b.ex, o.s, o.b.t)}>{t.buy}</button>
              </div>

              <div style={{background:'linear-gradient(180deg, rgba(20,18,26,0.92) 0%, rgba(12,11,18,0.96) 100%)', padding:'11px', borderRadius:'16px', border:'1px solid rgba(255,255,255,0.07)', boxShadow:'inset 0 1px 0 rgba(255,255,255,0.03)'}}>
                <div style={{fontSize:'12px', color:'#ff6f7d', fontWeight:'900', letterSpacing:'0.01em'}}>
                  {o.s1.ex} <span className="badge" style={{background: o.s1.t === 'S' ? '#3e8e41' : '#222'}}>{o.s1.t === 'S' ? 'SPOT' : 'FUT'}</span>
                </div>
                <div style={{fontSize:'16px', margin:'6px 0 5px', fontWeight:'900', color:'#fff'}}>{formatCardPrice(o, 'sell')}</div>
                <div style={{fontSize:'9px', color:'#7f909b'}}>Vol: {Math.round(o.s1.v)}</div>
                {o.s1.t === 'F' && o.s1.f != null && (<div style={{fontSize:'9px', color:'#ffeb3b'}}>Funding: {(o.s1.f*100).toFixed(4)}%</div>)}
                <button type="button" className="btn-sell" onClick={() => openExchangeMarket(o.s1.ex, o.s, o.s1.t)}>{t.sell}</button>
              </div>
            </div>

            {o.b.t === 'S' && o.s1.t === 'S' && o.routes && o.routes.length > 0 && (() => {
              const allRoutes = Array.isArray(o.routes) ? o.routes : [];
              const bestRoute = (o.depthCheck && o.depthCheck.bestRoute) ? o.depthCheck.bestRoute : (allRoutes[0] || null);
              const otherRoutes = bestRoute ? allRoutes.filter((r) => String(r.chain) !== String(bestRoute.chain)) : allRoutes.slice(1);
              const bestFeeUsd = bestRoute && bestRoute.feeUsd != null
                ? Number(bestRoute.feeUsd)
                : (o.depthCheck && o.depthCheck.routeFeeUsd != null ? Number(o.depthCheck.routeFeeUsd) : ((bestRoute && bestRoute.fee != null && Number(o.b.p) > 0) ? Number(bestRoute.fee) * Number(o.b.p) : null));
              const bestProfitUsd = bestRoute && bestRoute.estimatedProfitUsd != null
                ? Number(bestRoute.estimatedProfitUsd)
                : (o.depthCheck && o.depthCheck.estimatedProfitUsd != null ? Number(o.depthCheck.estimatedProfitUsd) : null);
              return (
              <div className="spot-warning" style={{marginTop:'10px', borderRadius:'16px', border:'1px solid rgba(255,193,7,0.16)', background:'linear-gradient(180deg, rgba(41,31,6,0.32) 0%, rgba(28,22,6,0.18) 100%)', padding:'11px 12px'}}>
                <div style={{fontWeight:'900', color:'#ffe27a', letterSpacing:'0.02em', fontSize:'12px'}}>⚡ ПЕРЕВОД ДОСТУПЕН</div>
                <div style={{marginTop:'5px', fontSize:'10px', lineHeight: 1.4}}>
                  {bestRoute && (
                    <div>• Лучшая сеть: <b>{bestRoute.chain}</b> — комиссия <b>{bestFeeUsd != null && Number.isFinite(bestFeeUsd) ? ('$' + bestFeeUsd.toFixed(2)) : '—'}</b></div>
                  )}
                  <div style={{marginTop:'6px'}}>• Примерная прибыль с круга: <b>{bestProfitUsd != null && Number.isFinite(bestProfitUsd) ? ('$' + bestProfitUsd.toFixed(2)) : '$0.00'}</b></div>
                  {otherRoutes.length > 0 && (
                    <div style={{marginTop:'8px', fontSize:'10px', opacity:0.95}}>
                      <div style={{marginBottom:'3px'}}>• Другие сети:</div>
                      {otherRoutes.map((r) => {
                        const p = r && r.estimatedProfitUsd != null ? Number(r.estimatedProfitUsd) : null;
                        return <div key={r.chain} style={{paddingLeft:'10px'}}>{r.chain} — {p != null && Number.isFinite(p) ? ('$' + p.toFixed(2)) : '—'}</div>;
                      })}
                    </div>
                  )}
                  <div style={{marginTop:'6px'}}>• Сейчас живёт: <b>{formatLifeText(spreadLifeStats[o.id] && spreadLifeStats[o.id].currentSec != null ? spreadLifeStats[o.id].currentSec : 0)}</b></div>
                  <div>• Среднее время жизни: <b>{formatLifeText(spreadLifeStats[o.id] && spreadLifeStats[o.id].avgSec != null ? spreadLifeStats[o.id].avgSec : (spreadLifeStats[o.id] && spreadLifeStats[o.id].currentSec != null ? spreadLifeStats[o.id].currentSec : 0))}</b></div>
                </div>
              </div>
              );
            })()}
          </div>
        )) : (
          <div className="card" style={{textAlign:'center', color:'#666', fontWeight:'bold'}}>{t.empty}</div>
        )}

        {showPrices && <PriceModal />}

        {showNotify && (
          <div className="modal notify-modal" style={{maxHeight:'86vh', overflowY:'auto', padding:'22px 18px 28px', borderRadius:'24px', border:'1px solid rgba(0,212,255,0.10)', background:'linear-gradient(180deg, rgba(9,15,22,0.985) 0%, rgba(6,10,15,0.985) 100%)', boxShadow:'0 18px 40px rgba(0,0,0,0.38)'}}>
            <div style={{display:'flex', justifyContent:'space-between', alignItems:'flex-start', gap:'14px', marginBottom:'18px', flexWrap:'wrap'}}>
              <div style={{minWidth:0, flex:'1 1 300px'}}>
                <div style={{display:'flex', alignItems:'center', gap:'10px', flexWrap:'wrap'}}>
                  <h2 style={{color:'#00d4ff', margin:0, fontSize:'24px', fontWeight:'900', letterSpacing:'0.02em'}}>🔔 {t.notif_title}</h2>
                  <div style={{padding:'6px 10px', borderRadius:'999px', border:'1px solid rgba(0,212,255,0.18)', background:'rgba(0,212,255,0.07)', color:'#d7f8ff', fontSize:'11px', fontWeight:'800'}}>без скрытия монет из ленты</div>
                </div>
                <div style={{color:'#7f909b', fontSize:'12px', lineHeight:'1.55', marginTop:'8px'}}>Настраивайте схождение, расхождение и персональный ЧС уведомлений в одном окне. Логика сигналов не меняется — улучшается только подача и удобство.</div>
              </div>
              <div style={{display:'flex', gap:'10px', flexWrap:'wrap', alignItems:'stretch', marginLeft:'auto'}}>
                <div style={{minWidth:'98px', padding:'11px 13px', borderRadius:'16px', border:'1px solid rgba(0,212,255,0.18)', background:'linear-gradient(180deg, rgba(0,212,255,0.08) 0%, rgba(0,212,255,0.03) 100%)'}}>
                  <div style={{fontSize:'10px', textTransform:'uppercase', color:'#7fa7b3', letterSpacing:'0.05em'}}>Активных</div>
                  <div style={{fontSize:'20px', color:'#fff', fontWeight:'900', lineHeight:1, marginTop:'4px'}}>{userAlerts.length}</div>
                </div>
                <div style={{minWidth:'98px', padding:'11px 13px', borderRadius:'16px', border:'1px solid rgba(255,77,79,0.18)', background:'linear-gradient(180deg, rgba(255,77,79,0.08) 0%, rgba(255,77,79,0.03) 100%)'}}>
                  <div style={{fontSize:'10px', textTransform:'uppercase', color:'#b68a8a', letterSpacing:'0.05em'}}>В ЧС</div>
                  <div style={{fontSize:'20px', color:'#fff', fontWeight:'900', lineHeight:1, marginTop:'4px'}}>{alertBlacklist.length}</div>
                </div>
                <button onClick={() => setShowNotify(false)} style={{width:'44px', height:'44px', borderRadius:'14px', border:'1px solid rgba(255,255,255,0.08)', background:'rgba(255,255,255,0.035)', color:'#fff', fontSize:'28px', lineHeight:1, cursor:'pointer', flexShrink:0}}>✕</button>
              </div>
            </div>

            <div className="card" style={{margin:'0 0 16px 0', padding:'18px', border:'1px solid rgba(0,212,255,0.10)', borderRadius:'22px', background:'linear-gradient(180deg, rgba(8,15,22,0.98) 0%, rgba(7,11,16,0.98) 100%)', boxShadow:'0 12px 28px rgba(0,0,0,0.22)'}}>
              <div style={{display:'flex', justifyContent:'space-between', alignItems:'flex-start', gap:'12px', flexWrap:'wrap', marginBottom:'14px'}}>
                <div style={{minWidth:0, flex:'1 1 260px'}}>
                  <div style={{fontWeight:'900', color:'#fff', fontSize:'19px'}}>Схождение</div>
                  <div style={{marginTop:'6px', fontSize:'12px', color:'#7b8b96', lineHeight:'1.55'}}>Один порог. После достижения выбранного процента уведомления продолжают приходить и при более низком спреде, пока вы не удалите сценарий вручную.</div>
                </div>
                <div style={{padding:'7px 11px', borderRadius:'999px', border:'1px solid rgba(255,193,7,0.32)', background:'rgba(255,193,7,0.08)', color:'#ffd760', fontSize:'11px', fontWeight:'800', whiteSpace:'nowrap'}}>до порога и ниже</div>
              </div>

              <div className="input-grid" style={{gridTemplateColumns:'1fr 1fr', gap:'10px', marginTop:0}}>
                <div className="input-wrap"><label>Монета</label><input type="text" value={convAlertForm.coin} onChange={e => setConvAlertForm(p => ({ ...p, coin: e.target.value.toUpperCase() }))} placeholder="BTC" /></div>
                <div className="input-wrap"><label>Уведомлять при спреде %</label><input type="number" value={convAlertForm.spreadFrom} onChange={e => setConvAlertForm(p => ({ ...p, spreadFrom: e.target.value }))} placeholder="2" /></div>
              </div>

              <div style={{marginTop:'14px'}}>
                <div style={{fontSize:'10px', color:'#00d4ff', textTransform:'uppercase', marginBottom:'6px', fontWeight:'700'}}>Покупка: тип рынка</div>
                <div style={{display:'flex', gap:'8px', flexWrap:'wrap'}}>
                  <button className={"btn-ex " + (convAlertForm.buyMarketType === 'S' ? 'active-ex' : '')} onClick={() => setConvAlertForm(p => ({ ...p, buyMarketType: 'S' }))}>Spot покупка</button>
                  <button className={"btn-ex " + (convAlertForm.buyMarketType === 'F' ? 'active-ex' : '')} onClick={() => setConvAlertForm(p => ({ ...p, buyMarketType: 'F' }))}>Futures покупка</button>
                </div>
              </div>

              <div className="input-grid" style={{gridTemplateColumns:'1fr 1fr', gap:'10px', marginTop:'12px'}}>
                <div className="input-wrap">
                  <label>Биржа покупки</label>
                  <select value={convAlertForm.buyExchange} onChange={e => setConvAlertForm(p => ({ ...p, buyExchange: e.target.value }))}>
                    {EXCHANGES.map(ex => <option key={'conv_buy_' + ex} value={ex}>{ex}</option>)}
                  </select>
                </div>
                <div className="input-wrap">
                  <label>Биржа продажи</label>
                  <select value={convAlertForm.sellExchange} onChange={e => setConvAlertForm(p => ({ ...p, sellExchange: e.target.value }))}>
                    {EXCHANGES.map(ex => <option key={'conv_sell_' + ex} value={ex}>{ex}</option>)}
                  </select>
                </div>
              </div>

              <div style={{marginTop:'12px', padding:'12px 14px', borderRadius:'16px', background:'rgba(255,255,255,0.03)', border:'1px solid rgba(255,255,255,0.06)', color:'#90a2ae', fontSize:'12px', lineHeight:'1.55'}}>Продажа для схождения всегда рассматривается как <b style={{color:'#fff'}}>Futures / Short</b>.</div>
              <button onClick={saveConvergenceAlert} style={{width:'100%', padding:'16px', background:'#18c3e8', border:'none', borderRadius:'16px', fontWeight:'900', marginTop:'16px', color:'#000', fontSize:'16px', boxShadow:'0 10px 20px rgba(24,195,232,0.18)'}}>{alertBusy ? 'Сохраняем...' : 'Добавить уведомление'}</button>
            </div>

            <div className="card" style={{margin:'0 0 16px 0', padding:'18px', border:'1px solid rgba(0,212,255,0.10)', borderRadius:'22px', background:'linear-gradient(180deg, rgba(8,15,22,0.98) 0%, rgba(7,11,16,0.98) 100%)', boxShadow:'0 12px 28px rgba(0,0,0,0.22)'}}>
              <div style={{display:'flex', justifyContent:'space-between', alignItems:'flex-start', gap:'12px', flexWrap:'wrap', marginBottom:'14px'}}>
                <div style={{minWidth:0, flex:'1 1 260px'}}>
                  <div style={{fontWeight:'900', color:'#fff', fontSize:'19px'}}>Расхождение</div>
                  <div style={{marginTop:'6px', fontSize:'12px', color:'#7b8b96', lineHeight:'1.55'}}>Диапазон от и до. Подходит для сценариев, где важен конкретный коридор спреда и набор бирж.</div>
                </div>
                <div style={{padding:'7px 11px', borderRadius:'999px', border:'1px solid rgba(0,212,255,0.32)', background:'rgba(0,212,255,0.08)', color:'#7be7ff', fontSize:'11px', fontWeight:'800', whiteSpace:'nowrap'}}>от и до</div>
              </div>

              <div style={{display:'flex', gap:'8px', marginBottom:'12px', flexWrap:'wrap'}}>
                <button className={"btn-ex " + (divAlertForm.coinMode === 'all' ? 'active-ex' : '')} onClick={() => setDivAlertForm(p => ({ ...p, coinMode: 'all' }))}>Все монеты</button>
                <button className={"btn-ex " + (divAlertForm.coinMode === 'single' ? 'active-ex' : '')} onClick={() => setDivAlertForm(p => ({ ...p, coinMode: 'single' }))}>Одна монета</button>
              </div>

              <div className="input-grid" style={{gridTemplateColumns: (divAlertForm.coinMode === 'single' ? '1fr 1fr 1fr' : '1fr 1fr'), gap:'10px', marginTop:0}}>
                {divAlertForm.coinMode === 'single' && (
                  <div className="input-wrap"><label>Монета</label><input type="text" value={divAlertForm.coin} onChange={e => setDivAlertForm(p => ({ ...p, coin: e.target.value.toUpperCase() }))} placeholder="BTC" /></div>
                )}
                <div className="input-wrap"><label>Спред от %</label><input type="number" value={divAlertForm.spreadFrom} onChange={e => setDivAlertForm(p => ({ ...p, spreadFrom: e.target.value }))} placeholder="1" /></div>
                <div className="input-wrap"><label>Спред до %</label><input type="number" value={divAlertForm.spreadTo} onChange={e => setDivAlertForm(p => ({ ...p, spreadTo: e.target.value }))} placeholder="необязательно" /></div>
              </div>

              <div style={{marginTop:'14px'}}>
                <div style={{fontSize:'10px', color:'#00d4ff', textTransform:'uppercase', marginBottom:'6px', fontWeight:'700'}}>Покупка: тип рынка</div>
                <div style={{display:'flex', gap:'8px', flexWrap:'wrap'}}>
                  <button className={"btn-ex " + ((divAlertForm.buyMarketTypes || []).includes('S') ? 'active-ex' : '')} onClick={() => toggleMarketType('S')}>Spot покупка</button>
                  <button className={"btn-ex " + ((divAlertForm.buyMarketTypes || []).includes('F') ? 'active-ex' : '')} onClick={() => toggleMarketType('F')}>Futures покупка</button>
                </div>
              </div>

              <div style={{marginTop:'14px'}}>
                <div style={{display:'flex', justifyContent:'space-between', alignItems:'center', gap:'8px', flexWrap:'wrap'}}>
                  <div style={{fontSize:'10px', color:'#00d4ff', textTransform:'uppercase', fontWeight:'700'}}>Биржи покупки</div>
                  <button className="btn-ex active-ex" onClick={() => setAllExchangesInForm('buyExchanges')}>Все</button>
                </div>
                <div style={{display:'flex', gap:'8px', flexWrap:'wrap', marginTop:'6px'}}>
                  {EXCHANGES.map(ex => (
                    <button key={'div_bx_' + ex} className={"btn-ex " + ((divAlertForm.buyExchanges || []).includes(ex) ? 'active-ex' : '')} onClick={() => toggleExchangeInForm('buyExchanges', ex)}>{ex}</button>
                  ))}
                </div>
              </div>

              <div style={{marginTop:'14px'}}>
                <div style={{display:'flex', justifyContent:'space-between', alignItems:'center', gap:'8px', flexWrap:'wrap'}}>
                  <div style={{fontSize:'10px', color:'#00d4ff', textTransform:'uppercase', fontWeight:'700'}}>Биржи продажи</div>
                  <button className="btn-ex active-ex" onClick={() => setAllExchangesInForm('sellExchanges')}>Все</button>
                </div>
                <div style={{display:'flex', gap:'8px', flexWrap:'wrap', marginTop:'6px'}}>
                  {EXCHANGES.map(ex => (
                    <button key={'div_sx_' + ex} className={"btn-ex " + ((divAlertForm.sellExchanges || []).includes(ex) ? 'active-ex' : '')} onClick={() => toggleExchangeInForm('sellExchanges', ex)}>{ex}</button>
                  ))}
                </div>
              </div>

              <div style={{marginTop:'12px', padding:'12px 14px', borderRadius:'16px', background:'rgba(255,255,255,0.03)', border:'1px solid rgba(255,255,255,0.06)', color:'#90a2ae', fontSize:'12px', lineHeight:'1.55'}}>Продажа для расхождения тоже всегда рассматривается как <b style={{color:'#fff'}}>Futures / Short</b>.</div>
              <button onClick={saveDivergenceAlert} style={{width:'100%', padding:'16px', background:'#18c3e8', border:'none', borderRadius:'16px', fontWeight:'900', marginTop:'16px', color:'#000', fontSize:'16px', boxShadow:'0 10px 20px rgba(24,195,232,0.18)'}}>{alertBusy ? 'Сохраняем...' : 'Добавить уведомление'}</button>
            </div>

            <div className="card" style={{margin:'0 0 16px 0', padding:'18px', border:'1px solid rgba(255,77,79,0.16)', borderRadius:'22px', background:'linear-gradient(180deg, rgba(18,12,14,0.98) 0%, rgba(11,8,9,0.98) 100%)', boxShadow:'0 12px 28px rgba(0,0,0,0.22)'}}>
              <div style={{display:'flex', justifyContent:'space-between', alignItems:'flex-start', gap:'12px', flexWrap:'wrap'}}>
                <div style={{minWidth:0, flex:'1 1 260px'}}>
                  <div style={{fontWeight:'900', color:'#ff6b6b', fontSize:'19px'}}>🚫 ЧС уведомлений</div>
                  <div style={{marginTop:'6px', fontSize:'12px', color:'#918285', lineHeight:'1.55'}}>Монеты из этого списка не будут присылать уведомления, но в основном списке связок останутся видимыми.</div>
                </div>
                <div style={{padding:'7px 11px', borderRadius:'999px', border:'1px solid rgba(255,77,79,0.28)', background:'rgba(255,77,79,0.08)', color:'#ffb0b0', fontSize:'11px', fontWeight:'800', whiteSpace:'nowrap'}}>только для алертов</div>
              </div>

              <div style={{display:'grid', gridTemplateColumns:'1fr auto', gap:'10px', alignItems:'end', marginTop:'14px'}}>
                <div className="input-wrap" style={{margin:0}}>
                  <label>Монета</label>
                  <input type="text" value={alertBlacklistInput} onChange={e => setAlertBlacklistInput(e.target.value.toUpperCase())} placeholder="BTC" />
                </div>
                <button className="btn-ex active-ex" onClick={() => addAlertBlacklistCoin(alertBlacklistInput)} style={{height:'44px', minWidth:'116px'}}>Добавить</button>
              </div>

              <div style={{display:'flex', gap:'8px', flexWrap:'wrap', marginTop:'12px'}}>
                {alertBlacklist.length ? alertBlacklist.map((coin) => (
                  <div key={'alert_bl_' + coin} style={{display:'inline-flex', alignItems:'center', gap:'8px', padding:'8px 12px', borderRadius:'999px', border:'1px solid rgba(255,77,79,0.30)', color:'#fff', background:'rgba(255,77,79,0.08)', fontWeight:'800', minHeight:'38px'}}>
                    <span>{coin}</span>
                    <span onClick={() => removeAlertBlacklistCoin(coin)} style={{cursor:'pointer', color:'#ff8a8a', fontWeight:'900', lineHeight:1}}>✕</span>
                  </div>
                )) : (
                  <div className="card" style={{width:'100%', margin:0, textAlign:'center', color:'#6f777d', background:'rgba(255,255,255,0.02)', border:'1px dashed rgba(255,255,255,0.08)', padding:'14px'}}>Список пуст</div>
                )}
              </div>
            </div>

            <div style={{display:'flex', justifyContent:'space-between', alignItems:'center', gap:'12px', flexWrap:'wrap', marginTop:'22px', marginBottom:'10px'}}>
              <div style={{minWidth:0, flex:'1 1 260px'}}>
                <div style={{fontWeight:'900', color:'#fff', fontSize:'18px'}}>Мои уведомления</div>
                <div style={{marginTop:'6px', fontSize:'12px', color:'#7b8b96', lineHeight:'1.55'}}>Все активные сценарии в одном списке. Удаление отключает уведомления, но не влияет на показ монеты в общей ленте.</div>
              </div>
              <div style={{minWidth:'42px', height:'42px', padding:'0 12px', borderRadius:'999px', display:'inline-flex', alignItems:'center', justifyContent:'center', background:'rgba(0,212,255,0.08)', border:'1px solid rgba(0,212,255,0.25)', color:'#d8f6ff', fontSize:'14px', fontWeight:'900'}}>{userAlerts.length}</div>
            </div>

            {userAlerts.length ? userAlerts.map((a) => {
              const buyModes = Array.isArray(a.buyMarketTypes) && a.buyMarketTypes.length
                ? a.buyMarketTypes.map(x => x === 'F' ? 'Futures' : 'Spot').join(' + ')
                : ((String(a.buyMarketType || a.marketType || 'S').toUpperCase() === 'F') ? 'Futures' : 'Spot');
              const title = a.direction === 'divergence' ? 'Расхождение' : 'Схождение';
              const coinLabel = a.direction === 'divergence'
                ? ((a.coinMode === 'single' && a.coin) ? a.coin : 'Все монеты')
                : (a.coin || '—');
              const buyExLabel = Array.isArray(a.buyExchanges) && a.buyExchanges.length ? a.buyExchanges.join(', ') : (a.buyExchange || '—');
              const sellExLabel = Array.isArray(a.sellExchanges) && a.sellExchanges.length ? a.sellExchanges.join(', ') : (a.sellExchange || '—');
              const rangeLabel = formatAlertRangeLabel(a);
              return (
                <div key={a.id} className="card" style={{margin:'10px 0 0 0', padding:'16px', border:'1px solid rgba(0,212,255,0.10)', borderRadius:'20px', background:'linear-gradient(180deg, rgba(8,14,20,0.98) 0%, rgba(7,12,18,0.98) 100%)', overflow:'hidden', boxShadow:'0 12px 24px rgba(0,0,0,0.16)'}}>
                  <div style={{display:'flex', justifyContent:'space-between', gap:'12px', alignItems:'flex-start', flexWrap:'wrap'}}>
                    <div style={{minWidth:0, flex:'1 1 260px'}}>
                      <div style={{display:'flex', flexWrap:'wrap', alignItems:'center', gap:'8px'}}>
                        <div style={{fontWeight:'900', color:'#00d4ff', fontSize:'16px'}}>{title}</div>
                        <div style={{fontSize:'11px', color:'#d8f6ff', border:'1px solid rgba(0,212,255,0.30)', background:'rgba(0,212,255,0.08)', borderRadius:'999px', padding:'4px 8px', lineHeight:1.25}}>{rangeLabel}</div>
                        {a.direction === 'divergence' && <div style={{fontSize:'11px', color:'#ffd760', border:'1px solid rgba(255,193,7,0.24)', background:'rgba(255,193,7,0.08)', borderRadius:'999px', padding:'4px 8px', lineHeight:1.25}}>{a.coinMode === 'single' ? 'Одна монета' : 'Все монеты'}</div>}
                      </div>

                      <div style={{display:'flex', alignItems:'center', gap:'8px', fontSize:'16px', fontWeight:'800', color:'#fff', marginTop:'10px', lineHeight:'1.25'}}>
                        <span style={{width:'8px', height:'8px', borderRadius:'50%', background:'#00d4ff', boxShadow:'0 0 10px rgba(0,212,255,0.45)', flexShrink:0}}></span>
                        <span>{coinLabel}</span>
                      </div>

                      <div style={{display:'grid', gridTemplateColumns:'1fr 1fr', gap:'10px', marginTop:'12px'}}>
                        <div style={{border:'1px solid rgba(255,255,255,0.06)', background:'rgba(255,255,255,0.025)', borderRadius:'15px', padding:'12px'}}>
                          <div style={{fontSize:'11px', textTransform:'uppercase', color:'#7fa7b3', marginBottom:'6px', fontWeight:'700'}}>Покупка</div>
                          <div style={{color:'#fff', fontSize:'13px', fontWeight:'800', lineHeight:'1.45', wordBreak:'break-word'}}>{buyExLabel}</div>
                          <div style={{color:'#73d5e7', fontSize:'12px', marginTop:'4px', lineHeight:'1.4'}}>{buyModes}</div>
                        </div>
                        <div style={{border:'1px solid rgba(255,255,255,0.06)', background:'rgba(255,255,255,0.025)', borderRadius:'15px', padding:'12px'}}>
                          <div style={{fontSize:'11px', textTransform:'uppercase', color:'#7fa7b3', marginBottom:'6px', fontWeight:'700'}}>Продажа</div>
                          <div style={{color:'#fff', fontSize:'13px', fontWeight:'800', lineHeight:'1.45', wordBreak:'break-word'}}>{sellExLabel}</div>
                          <div style={{color:'#73d5e7', fontSize:'12px', marginTop:'4px', lineHeight:'1.4'}}>Futures / Short</div>
                        </div>
                      </div>

                      {a.lastCoin && a.direction === 'divergence' && <div style={{fontSize:'12px', color:'#9aa4ad', marginTop:'10px', lineHeight:'1.45'}}>Последняя монета: <span style={{color:'#fff', fontWeight:'700'}}>{a.lastCoin}</span></div>}
                    </div>

                    <button onClick={() => deleteUserAlert(a.id)} style={{background:'#170b0c', border:'1px solid rgba(255,119,119,0.24)', color:'#ff8b8b', borderRadius:'12px', padding:'9px 12px', minWidth:'90px', flexShrink:0, fontWeight:'800'}}>Удалить</button>
                  </div>
                </div>
              );
            }) : (
              <div className="card" style={{marginTop:'10px', textAlign:'center', color:'#66737b', background:'rgba(255,255,255,0.02)', border:'1px dashed rgba(255,255,255,0.08)', padding:'14px'}}>Уведомлений пока нет</div>
            )}

            <button onClick={() => setShowNotify(false)} style={{width:'100%', padding:'15px', background:'linear-gradient(180deg, #11151a 0%, #0d1014 100%)', border:'1px solid rgba(255,255,255,0.08)', borderRadius:'16px', fontWeight:'900', marginTop:'18px', color:'#fff'}}>{t.close}</button>
          </div>
        )}



        {showSettings && (
          <div className="modal" style={{maxHeight:'86vh', overflowY:'auto', padding:'22px 18px 26px', borderRadius:'26px', border:'1px solid rgba(0,212,255,0.10)', background:'linear-gradient(180deg, rgba(9,15,22,0.985) 0%, rgba(6,10,15,0.985) 100%)', boxShadow:'0 18px 40px rgba(0,0,0,0.38)'}}>
            <div style={{display:'flex', justifyContent:'space-between', alignItems:'flex-start', gap:'14px', marginBottom:'18px', flexWrap:'wrap'}}>
              <div style={{minWidth:0, flex:'1 1 260px'}}>
                <div style={{display:'flex', alignItems:'center', gap:'10px', flexWrap:'wrap'}}>
                  <h2 style={{color:'#00d4ff', margin:0, fontSize:'24px', fontWeight:'900', letterSpacing:'0.02em'}}>⚙️ {t.settings}</h2>
                  <div style={{padding:'6px 10px', borderRadius:'999px', border:'1px solid rgba(0,212,255,0.18)', background:'rgba(0,212,255,0.07)', color:'#d7f8ff', fontSize:'11px', fontWeight:'800'}}>режимы и фильтры</div>
                </div>
                <div style={{color:'#7f909b', fontSize:'12px', lineHeight:'1.55', marginTop:'8px'}}>Здесь включаются рабочие режимы сканера.</div>
              </div>
              <button onClick={() => setShowSettings(false)} style={{width:'44px', height:'44px', borderRadius:'14px', border:'1px solid rgba(255,255,255,0.08)', background:'rgba(255,255,255,0.035)', color:'#fff', fontSize:'28px', lineHeight:1, cursor:'pointer', flexShrink:0}}>✕</button>
            </div>

            <div className="card" style={{margin:0, padding:'16px', border:'1px solid rgba(0,212,255,0.10)', borderRadius:'22px', background:'linear-gradient(180deg, rgba(8,15,22,0.98) 0%, rgba(7,11,16,0.98) 100%)', boxShadow:'0 12px 28px rgba(0,0,0,0.22)'}}>
              <div style={{fontWeight:'900', color:'#fff', fontSize:'18px', marginBottom:'14px'}}>Режимы сканирования</div>

              {[
                {label: t.s2f, value: arbS2F, toggle: switchS2FMode, accent:'rgba(0,212,255,0.08)'},
                {label: t.f2f, value: arbF2F, toggle: switchF2FMode, accent:'rgba(0,212,255,0.08)'},
                {label: t.s2s, value: arbS2S, toggle: switchS2SMode, accent:'rgba(255,193,7,0.08)'}
              ].map((item) => (
                <div key={item.label} style={{display:'flex', justifyContent:'space-between', alignItems:'center', gap:'14px', padding:'14px 15px', borderRadius:'18px', border:'1px solid rgba(255,255,255,0.06)', background:item.accent, marginTop:'10px'}}>
                  <div style={{minWidth:0, flex:'1 1 auto'}}>
                    <div style={{color:'#fff', fontWeight:'800', lineHeight:'1.35'}}>{item.label}</div>
                    <div style={{marginTop:'4px', color:'#7f909b', fontSize:'12px', lineHeight:'1.45'}}>{item.value ? 'Режим включён и участвует в поиске.' : 'Режим временно выключен.'}</div>
                  </div>
                  <button className={'btn-toggle ' + (item.value ? 'on' : '')} onClick={item.toggle}>{item.value ? t.on : t.off}</button>
                </div>
              ))}
            </div>

            {userData && userData.isMainAdmin && (
              <div className="card" style={{marginTop:'16px', padding:'18px', border:'1px solid rgba(255,235,59,0.18)', borderRadius:'22px', background:'linear-gradient(180deg, rgba(28,23,8,0.98) 0%, rgba(16,13,6,0.98) 100%)', boxShadow:'0 12px 28px rgba(0,0,0,0.22)'}}>
                <div style={{display:'flex', justifyContent:'space-between', alignItems:'flex-start', gap:'12px', flexWrap:'wrap'}}>
                  <div style={{minWidth:0, flex:'1 1 250px'}}>
                    <div style={{color:'#ffeb3b', fontWeight:'900', fontSize:'18px'}}>🛡 Guard-список тикеров</div>
                    <div style={{color:'#b8af75', fontSize:'12px', lineHeight:'1.55', marginTop:'6px'}}>Тикеры из этого списка проходят дополнительную проверку для всех пользователей. Добавление и удаление применяется сразу.</div>
                  </div>
                  <div style={{padding:'6px 10px', borderRadius:'999px', border:'1px solid rgba(255,235,59,0.24)', background:'rgba(255,235,59,0.08)', color:'#ffef8d', fontSize:'11px', fontWeight:'800'}}>админ</div>
                </div>

                <div style={{display:'grid', gridTemplateColumns:'1fr auto', gap:'10px', marginTop:'14px', alignItems:'end'}}>
                  <div className="input-wrap" style={{margin:0}}>
                    <label>Тикер</label>
                    <input
                      value={adminGuardInput}
                      onChange={(e) => setAdminGuardInput(String(e.target.value || '').toUpperCase())}
                      placeholder="Например: EDGE"
                      style={{flex:1, background:'#111', color:'#fff', border:'1px solid #333', borderRadius:'14px', padding:'12px'}}
                    />
                  </div>
                  <button
                    onClick={addAdminGuardSymbol}
                    disabled={adminGuardBusy}
                    style={{height:'44px', background:'#ffeb3b', color:'#000', border:'none', borderRadius:'14px', padding:'12px 16px', fontWeight:'900', opacity: adminGuardBusy ? 0.7 : 1}}
                  >Добавить</button>
                </div>

                <div style={{display:'flex', flexWrap:'wrap', gap:'8px', marginTop:'12px'}}>
                  {adminGuardSymbols.length ? adminGuardSymbols.map((sym) => (
                    <button
                      key={sym}
                      onClick={() => removeAdminGuardSymbol(sym)}
                      style={{background:'#111', color:'#ffeb3b', border:'1px solid #4a4020', borderRadius:'999px', padding:'8px 12px', fontWeight:'800'}}
                    >{sym} ×</button>
                  )) : (
                    <div style={{width:'100%', color:'#8d8763', fontSize:'12px', textAlign:'center', padding:'14px', borderRadius:'16px', border:'1px dashed rgba(255,235,59,0.16)', background:'rgba(255,255,255,0.02)'}}>Список пока пуст</div>
                  )}
                </div>
              </div>
            )}

            <button onClick={() => setShowSettings(false)} style={{width:'100%', padding:'15px', background:'#18c3e8', border:'none', borderRadius:'16px', fontWeight:'900', marginTop:'18px', color:'#000', fontSize:'15px', boxShadow:'0 10px 20px rgba(24,195,232,0.18)'}}>{t.save}</button>
          </div>
        )}


        {showProfile && userData && (
          <div className="modal" style={{maxHeight:'86vh', overflowY:'auto', padding:'22px 18px 26px', borderRadius:'26px', border:'1px solid rgba(0,212,255,0.10)', background:'linear-gradient(180deg, rgba(9,15,22,0.985) 0%, rgba(6,10,15,0.985) 100%)', boxShadow:'0 18px 40px rgba(0,0,0,0.38)'}}>
            <div style={{display:'flex', justifyContent:'space-between', alignItems:'flex-start', gap:'14px', marginBottom:'18px', flexWrap:'wrap'}}>
              <div style={{minWidth:0, flex:'1 1 260px'}}>
                <div style={{display:'flex', alignItems:'center', gap:'10px', flexWrap:'wrap'}}>
                  <h2 style={{color:'#00d4ff', margin:0, fontSize:'24px', fontWeight:'900', letterSpacing:'0.02em'}}>👤 {t.profile}</h2>
                  <div style={{padding:'6px 10px', borderRadius:'999px', border:'1px solid rgba(0,212,255,0.18)', background:'rgba(0,212,255,0.07)', color:'#d7f8ff', fontSize:'11px', fontWeight:'800'}}>личный кабинет</div>
                </div>
                <div style={{color:'#7f909b', fontSize:'12px', lineHeight:'1.55', marginTop:'8px'}}>Профиль, язык, статус подписки и быстрые действия собраны в одном аккуратном блоке.</div>
              </div>
              <button onClick={() => setShowProfile(false)} style={{width:'44px', height:'44px', borderRadius:'14px', border:'1px solid rgba(255,255,255,0.08)', background:'rgba(255,255,255,0.035)', color:'#fff', fontSize:'28px', lineHeight:1, cursor:'pointer', flexShrink:0}}>✕</button>
            </div>

            <div className="card" style={{margin:0, padding:'18px', border:'1px solid rgba(0,212,255,0.10)', borderRadius:'22px', background:'linear-gradient(180deg, rgba(8,15,22,0.98) 0%, rgba(7,11,16,0.98) 100%)', boxShadow:'0 12px 28px rgba(0,0,0,0.22)'}}>
              <div style={{display:'flex', justifyContent:'space-between', gap:'12px', alignItems:'flex-start', flexWrap:'wrap'}}>
                <div style={{minWidth:0, flex:'1 1 220px'}}>
                  <div style={{fontSize:'20px', color:'#00d4ff', fontWeight:'900', lineHeight:'1.2'}}>{tgName || 'User'}</div>
                  <div style={{fontSize:'12px', color:'#7f909b', marginTop:'6px'}}>ID: {tgId || ''}</div>
                </div>
                <div style={{display:'inline-flex', padding:'5px', borderRadius:'14px', background:'rgba(255,255,255,0.03)', border:'1px solid rgba(255,255,255,0.06)', gap:'6px'}}>
                  <button onClick={() => setLang('ru')} style={{background: lang==='ru'?'#18c3e8':'transparent', color: lang==='ru'?'#000':'#fff', border:'none', padding:'8px 14px', borderRadius:'10px', fontWeight:'900'}}>RU</button>
                  <button onClick={() => setLang('en')} style={{background: lang==='en'?'#18c3e8':'transparent', color: lang==='en'?'#000':'#fff', border:'none', padding:'8px 14px', borderRadius:'10px', fontWeight:'900'}}>EN</button>
                </div>
              </div>

              {userData.isAdmin && userData.stats ? (
                <div style={{marginTop:'18px'}}>
                  <div style={{color:'#ffeb3b', fontWeight:'900', fontSize:'17px', marginBottom:'12px'}}>👑 {t.admin_panel}</div>
                  <div style={{display:'grid', gap:'10px'}}>
                    <div className="stat-box" style={{borderRadius:'16px', padding:'14px 16px'}}><div>{t.total_users}</div><div className="stat-val">{userData.stats.totalUsers}</div></div>
                    <div className="stat-box" style={{borderRadius:'16px', padding:'14px 16px'}}><div>{t.active_subs}</div><div className="stat-val" style={{color:'#00ff64'}}>{userData.stats.activeSubs}</div></div>
                    <div className="stat-box" style={{borderRadius:'16px', padding:'14px 16px'}}><div>{t.online}</div><div className="stat-val" style={{color:'#ffeb3b'}}>{userData.stats.onlineNow}</div></div>
                  </div>
                </div>
              ) : (
                <div style={{marginTop:'18px'}}>
                  <div style={{padding:'12px 14px', background:'rgba(0,255,100,0.10)', border:'1px solid rgba(0,255,100,0.35)', borderRadius:'16px', color:'#00ff64', fontWeight:'900', textAlign:'center'}}>{t.sub_active}</div>
                  <div style={{marginTop:'12px', display:'grid', gap:'10px'}}>
                    <div style={{display:'flex', justifyContent:'space-between', alignItems:'center', background:'rgba(255,255,255,0.03)', border:'1px solid rgba(255,255,255,0.06)', padding:'14px 15px', borderRadius:'16px'}}>
                      <span style={{color:'#7f909b'}}>{t.days_left}</span>
                      <b style={{color:'#fff', fontSize:'16px'}}>{userData.daysLeft}</b>
                    </div>
                    {userData.subEndDate && (
                      <div style={{padding:'12px 14px', borderRadius:'16px', background:'rgba(255,255,255,0.02)', border:'1px solid rgba(255,255,255,0.05)', fontSize:'12px', color:'#9aa4ad'}}>
                        {t.end_date} <span style={{color:'#fff', fontWeight:'700'}}>{new Date(userData.subEndDate).toLocaleDateString()}</span>
                      </div>
                    )}
                  </div>
                </div>
              )}

              <div style={{marginTop:'16px', display:'grid', gridTemplateColumns:'1fr 1fr', gap:'10px'}}>
                <button onClick={() => setShowRef(true)} style={{width:'100%', padding:'14px 12px', background:'linear-gradient(180deg, rgba(8,17,26,1) 0%, rgba(10,13,18,1) 100%)', border:'1px solid rgba(0,212,255,0.20)', borderRadius:'16px', color:'#00d4ff', fontWeight:'900', fontSize:'12px', lineHeight:1.2}}>🤝 {t.referral_short}</button>
                <button onClick={openGuide} style={{width:'100%', padding:'14px 12px', background:'linear-gradient(180deg, rgba(8,17,26,1) 0%, rgba(10,13,18,1) 100%)', border:'1px solid rgba(0,212,255,0.20)', borderRadius:'16px', color:'#00d4ff', fontWeight:'900', fontSize:'12px', lineHeight:1.2}}>📘 {t.guide}</button>
              </div>

              <button className="btn-pay" onClick={() => { setShowProfile(false); setShowPrices(true); }} style={{marginTop:'18px'}}>{t.pay_btn}</button>
            </div>
          </div>
        )}


        

{showRef && (
  <div className="modal" style={{maxHeight:'86vh', overflowY:'auto', padding:'22px 18px 26px', borderRadius:'26px', border:'1px solid rgba(0,212,255,0.10)', background:'linear-gradient(180deg, rgba(9,15,22,0.985) 0%, rgba(6,10,15,0.985) 100%)', boxShadow:'0 18px 40px rgba(0,0,0,0.38)'}}>
    <div style={{display:'flex', justifyContent:'space-between', alignItems:'flex-start', gap:'14px', marginBottom:'18px', flexWrap:'wrap'}}>
      <div style={{minWidth:0, flex:'1 1 260px'}}>
        <div style={{display:'flex', alignItems:'center', gap:'10px', flexWrap:'wrap'}}>
          <h2 style={{color:'#00d4ff', margin:0, fontSize:'24px', fontWeight:'900', letterSpacing:'0.02em'}}>🤝 Реферальная программа</h2>
          <div style={{padding:'6px 10px', borderRadius:'999px', border:'1px solid rgba(0,212,255,0.18)', background:'rgba(0,212,255,0.07)', color:'#d7f8ff', fontSize:'11px', fontWeight:'800'}}>приглашай и получай дни</div>
        </div>
        <div style={{color:'#7f909b', fontSize:'12px', lineHeight:'1.55', marginTop:'8px'}}>Окно приведено к единому стилю: ссылка, награды и бонусы от бирж читаются проще, но логика начислений не меняется.</div>
      </div>
      <button onClick={() => setShowRef(false)} style={{width:'44px', height:'44px', borderRadius:'14px', border:'1px solid rgba(255,255,255,0.08)', background:'rgba(255,255,255,0.035)', color:'#fff', fontSize:'28px', lineHeight:1, cursor:'pointer', flexShrink:0}}>✕</button>
    </div>

    <div className="card" style={{margin:0, padding:'18px', border:'1px solid rgba(0,212,255,0.10)', borderRadius:'22px', background:'linear-gradient(180deg, rgba(8,15,22,0.98) 0%, rgba(7,11,16,0.98) 100%)', boxShadow:'0 12px 28px rgba(0,0,0,0.22)', lineHeight:1.55}}>
      <div style={{padding:'12px 14px', borderRadius:'16px', background:'rgba(0,255,100,0.08)', border:'1px solid rgba(0,255,100,0.18)', color:'#9ef6b8', fontWeight:'900', marginBottom:'14px'}}>Приглашай друзей и получай дни доступа 🎁</div>

      <div style={{color:'#9aa4ad', fontSize:'12px'}}>
        <b style={{color:'#fff'}}>Твоя реф. ссылка на бота:</b>
        <div style={{display:'flex', gap:'10px', alignItems:'center', justifyContent:'space-between', marginTop:'10px', padding:'12px 14px', borderRadius:'16px', border:'1px solid rgba(255,255,255,0.06)', background:'rgba(255,255,255,0.03)'}}>
          <span style={{wordBreak:'break-all', flex: 1, color:'#d9f8ff'}}>{userData.referralLink || '—'}</span>
          <button
            onClick={() => copyText(userData.referralLink)}
            style={{padding:'8px 12px', background:'#111', color:'#fff', border:'1px solid #333', borderRadius:'10px', fontWeight:'900', cursor:'pointer'}}
          >📋</button>
        </div>
      </div>

      <div style={{marginTop:'16px', color:'#fff', fontSize:'12px'}}>
        <b>Награды:</b>
        <div style={{display:'grid', gap:'8px', marginTop:'10px'}}>
          {[
            'Друг активировал пробный доступ — +1 сутки',
            'Оплатил 1 месяц — +2 суток',
            'Оплатил 3 месяца — +8 суток',
            'Оплатил 6 месяцев — +18 суток',
            'Оплатил 1 год — +30 суток'
          ].map((item) => (
            <div key={item} style={{padding:'11px 13px', borderRadius:'14px', border:'1px solid rgba(255,255,255,0.06)', background:'rgba(255,255,255,0.025)', color:'#d7e2e8'}}>{item}</div>
          ))}
        </div>
      </div>

      <div style={{marginTop:'16px', color:'#9aa4ad', fontSize:'12px'}}>
        <b style={{color:'#fff'}}>Бонусы от бирж:</b>
        <div style={{marginTop:'10px', display:'flex', flexWrap:'wrap', gap:'8px'}}>
          {(window.EXCHANGE_REF_LINKS || []).map((x) => (
            <a
              key={x.name}
              href={x.url}
              target="_blank"
              rel="noreferrer"
              style={{display:'inline-flex', alignItems:'center', padding:'8px 12px', borderRadius:'999px', border:'1px solid rgba(0,212,255,0.18)', background:'rgba(0,212,255,0.06)', color:'#00d4ff', textDecoration:'none', fontWeight:'700'}}
            >
              {x.name}
            </a>
          ))}
        </div>
      </div>

      <button className="btn-pay" onClick={() => setShowRef(false)} style={{marginTop:'18px'}}>ОК</button>
    </div>
  </div>
)}


          {showBl && (
          <div className="modal" style={{maxHeight:'86vh', overflowY:'auto', padding:'22px 18px 26px', borderRadius:'26px', border:'1px solid rgba(255,77,79,0.14)', background:'linear-gradient(180deg, rgba(16,10,12,0.985) 0%, rgba(9,7,8,0.985) 100%)', boxShadow:'0 18px 40px rgba(0,0,0,0.38)'}}>
            <div style={{display:'flex', justifyContent:'space-between', alignItems:'flex-start', gap:'14px', marginBottom:'18px', flexWrap:'wrap'}}>
              <div style={{minWidth:0, flex:'1 1 260px'}}>
                <div style={{display:'flex', alignItems:'center', gap:'10px', flexWrap:'wrap'}}>
                  <h2 style={{color:'#ff6b6b', margin:0, fontSize:'24px', fontWeight:'900', letterSpacing:'0.02em'}}>🚫 {t.bl}</h2>
                  <div style={{padding:'6px 10px', borderRadius:'999px', border:'1px solid rgba(255,77,79,0.24)', background:'rgba(255,77,79,0.08)', color:'#ffb0b0', fontSize:'11px', fontWeight:'800'}}>монеты скрыты только из ленты</div>
                </div>
                <div style={{color:'#a48b8f', fontSize:'12px', lineHeight:'1.55', marginTop:'8px'}}>Здесь хранятся монеты, которые вы исключили из общего списка. Возврат в ленту выполняется одной кнопкой без изменения других настроек.</div>
              </div>
              <button onClick={() => setShowBl(false)} style={{width:'44px', height:'44px', borderRadius:'14px', border:'1px solid rgba(255,255,255,0.08)', background:'rgba(255,255,255,0.035)', color:'#fff', fontSize:'28px', lineHeight:1, cursor:'pointer', flexShrink:0}}>✕</button>
            </div>

            <div className="card" style={{margin:0, padding:'18px', border:'1px solid rgba(255,77,79,0.14)', borderRadius:'22px', background:'linear-gradient(180deg, rgba(18,12,14,0.98) 0%, rgba(11,8,9,0.98) 100%)', boxShadow:'0 12px 28px rgba(0,0,0,0.22)'}}>
              <div style={{display:'flex', justifyContent:'space-between', alignItems:'center', gap:'12px', flexWrap:'wrap', marginBottom:'14px'}}>
                <div style={{fontWeight:'900', color:'#fff', fontSize:'18px'}}>Список исключённых монет</div>
                <div style={{minWidth:'42px', height:'42px', padding:'0 12px', borderRadius:'999px', display:'inline-flex', alignItems:'center', justifyContent:'center', background:'rgba(255,77,79,0.08)', border:'1px solid rgba(255,77,79,0.25)', color:'#ffd2d2', fontSize:'14px', fontWeight:'900'}}>{bl.length}</div>
              </div>

              <div style={{display:'flex', flexWrap:'wrap', gap:'10px'}}>
                {bl.length === 0 ? (
                  <div style={{width:'100%', color:'#8f7a7e', textAlign:'center', padding:'16px', borderRadius:'16px', border:'1px dashed rgba(255,255,255,0.08)', background:'rgba(255,255,255,0.02)'}}>{t.empty}</div>
                ) : bl.map(coin => (
                  <div key={coin} style={{background:'rgba(255,255,255,0.03)', padding:'10px 14px', borderRadius:'14px', border:'1px solid rgba(255,255,255,0.06)', display:'flex', alignItems:'center', gap:'10px'}}>
                    <b style={{fontSize:'14px', color:'#fff'}}>{coin}</b>
                    <button onClick={() => setBl(bl.filter(x => x !== coin))} style={{background:'transparent', border:'none', color:'#ff8b8b', cursor:'pointer', fontWeight:'900', fontSize:'12px'}}>{t.return}</button>
                  </div>
                ))}
              </div>
            </div>
          </div>
        )}


        {showScroll && (
          <button className="scroll-to-top" onClick={scrollToTop}>↑</button>
        )}
      </div>
    );
  };

  ReactDOM.createRoot(document.getElementById('root')).render(<ErrorBoundary><App /></ErrorBoundary>);
