// FURY · Direction C — Cinematic Post-Apoc
// Editorial serif headlines + warm cream highlights on deep warm-black,
// oxblood accent, atmospheric photo placeholders, generous breathing
// room. Magazine-style. 1440×900.

(function () {
  const { useShop, furyFmt, furyTone, FuryGlyph } = window;

  const C = {
    bg: '#0d0a08',
    panel: '#181410',
    panel2: '#221c16',
    line: '#3a2e24',
    lineSoft: '#2a221b',
    text: '#cfc2ad',
    hi: '#e8dccb',
    paper: '#e8dccb',
    dim: '#7a6e5f',
    blood: '#a82828',
    bloodHi: '#c93838',
    bloodDeep: '#5a1414',
    ember: '#d97a2a',
  };
  const F = {
    serif: '"Spectral", "Cormorant Garamond", Georgia, serif',
    sans: '"Inter", "Manrope", system-ui, sans-serif',
    mono: '"IBM Plex Mono", "Courier New", monospace',
  };

  function Display({ s = 38, italic, c, w = 500, children, style }) {
    return (
      <span style={{
        fontFamily: F.serif, fontSize: s, fontWeight: w,
        fontStyle: italic ? 'italic' : 'normal',
        color: c || C.hi, letterSpacing: '-0.01em', lineHeight: 1.1, ...style,
      }}>{children}</span>
    );
  }
  function Sans({ s = 12, w = 400, c, upper, tracked, children, style }) {
    return (
      <span style={{
        fontFamily: F.sans, fontSize: s, fontWeight: w,
        color: c || C.text, letterSpacing: tracked ? '0.16em' : 'normal',
        textTransform: upper ? 'uppercase' : 'none', ...style,
      }}>{children}</span>
    );
  }
  function Mono({ s = 10, c, children, style }) {
    return (
      <span style={{
        fontFamily: F.mono, fontSize: s, color: c || C.dim, letterSpacing: '0.05em', ...style,
      }}>{children}</span>
    );
  }

  // Статус → цвет точки-индикатора. Используется в админке (платежи, доставки,
  // подарки, покупки) и на главной — чтобы одинаково читалось «прошёл/ждёт/неуспех».
  function adminStatusColor(st) {
    const s = String(st || '').toLowerCase();
    if (s === 'paid' || s === 'succeeded' || s === 'delivered' || s === 'active' || s === 'claimed') return '#3c8c3c';
    if (s === 'pending' || s === 'awaiting_verification' || s === 'awaiting' || s === 'processing') return C.ember;
    if (s === 'refunded') return '#4a90d9';
    if (s === 'failed' || s === 'expired' || s === 'canceled' || s === 'cancelled' || s === 'rejected') return C.blood;
    return C.dim;
  }
  function adminStatusTitle(st, ru) {
    const s = String(st || '').toLowerCase();
    const map = ru
      ? { paid: 'оплачен', succeeded: 'оплачен', pending: 'в ожидании', awaiting_verification: 'ожидает проверки', failed: 'неуспех', expired: 'истёк', canceled: 'отменён', cancelled: 'отменён', refunded: 'возврат средств', delivered: 'доставлен', active: 'активен', claimed: 'получен', rejected: 'отклонён', processing: 'обработка' }
      : { paid: 'paid', succeeded: 'paid', pending: 'pending', awaiting_verification: 'verifying', failed: 'failed', expired: 'expired', canceled: 'canceled', cancelled: 'canceled', refunded: 'refunded', delivered: 'delivered', active: 'active', claimed: 'claimed', rejected: 'rejected', processing: 'processing' };
    return map[s] || s || '—';
  }

  // Сворачиваемая секция админки с сортировкой и опциональной точкой-статусом.
  // Вынесен из AdminUsers, чтобы локальное state (open/sort/dir) не сбрасывалось
  // при ре-рендере родителя.
  function AdminSection({
    ru, canManage, busy,
    title, rows, render, onDel, rowAction,
    sortable, defaultSort, defaultDir = 'desc',
    getDate, getAmount, getQty, getStatus,
    defaultOpen = false,
  }) {
    const sortKeys = sortable || [];
    const [open, setOpen] = React.useState(!!defaultOpen);
    const [sort, setSort] = React.useState(defaultSort || sortKeys[0] || null);
    const [dir,  setDir]  = React.useState(defaultDir);

    const sortedRows = React.useMemo(() => {
      if (!sort || rows.length < 2) return rows;
      const get =
        sort === 'date'   ? (getDate   || ((r) => r.created_at)) :
        sort === 'amount' ? (getAmount || ((r) => r.amount)) :
        sort === 'qty'    ? (getQty    || ((r) => r.qty)) : null;
      if (!get) return rows;
      const arr = rows.slice();
      arr.sort((a, b) => {
        const va = Number(get(a)); const vb = Number(get(b));
        const na = Number.isFinite(va) ? va : 0;
        const nb = Number.isFinite(vb) ? vb : 0;
        return dir === 'asc' ? na - nb : nb - na;
      });
      return arr;
    }, [rows, sort, dir, getDate, getAmount, getQty]);

    const sortLabel = {
      date:   ru ? 'По дате'      : 'By date',
      amount: ru ? 'По сумме'     : 'By amount',
      qty:    ru ? 'По количеству': 'By qty',
    };
    const selSm  = { background: 'transparent', border: `1px solid ${C.line}`, color: C.hi, padding: '4px 6px', fontFamily: F.sans, fontSize: 10, cursor: 'pointer' };
    const dirBtn = { background: 'transparent', border: `1px solid ${C.line}`, color: C.hi, padding: '3px 8px', fontFamily: F.sans, fontSize: 11, cursor: 'pointer', minWidth: 28 };
    const delBtn = { background: 'transparent', border: `1px solid ${C.line}`, color: '#d06868', cursor: 'pointer', padding: '4px 9px', fontFamily: F.sans, fontSize: 11, flexShrink: 0 };

    return (
      <div style={{ marginTop: 12, border: `1px solid ${C.line}`, background: C.panel2 }}>
        <div onClick={() => setOpen((o) => !o)} style={{
          display: 'flex', alignItems: 'center', gap: 10, padding: '10px 14px',
          cursor: 'pointer', userSelect: 'none', flexWrap: 'wrap',
        }}>
          <span style={{
            display: 'inline-block', fontSize: 10, color: C.ember, width: 10,
            transition: 'transform 0.15s ease', transform: open ? 'rotate(90deg)' : 'rotate(0)',
          }}>▶</span>
          <Sans s={11} c={C.ember} upper tracked>{title}</Sans>
          <Sans s={11} c={C.dim}>· {rows.length}</Sans>
          {open && sortKeys.length > 0 && rows.length > 1 && (
            <div onClick={(e) => e.stopPropagation()} style={{ display: 'flex', gap: 6, marginLeft: 'auto', alignItems: 'center' }}>
              <Sans s={9} c={C.dim} upper tracked>{ru ? 'Сорт' : 'Sort'}</Sans>
              <select value={sort || ''} onChange={(e) => setSort(e.target.value)} style={selSm}>
                {sortKeys.map((k) => (
                  <option key={k} value={k} style={{ background: C.bg }}>{sortLabel[k] || k}</option>
                ))}
              </select>
              <button onClick={() => setDir((d) => d === 'asc' ? 'desc' : 'asc')} style={dirBtn}
                title={dir === 'asc' ? (ru ? 'возрастание' : 'ascending') : (ru ? 'убывание' : 'descending')}>
                {dir === 'asc' ? '↑' : '↓'}
              </button>
            </div>
          )}
        </div>
        {open && (
          <div style={{ borderTop: `1px solid ${C.line}`, padding: 8 }}>
            {rows.length === 0 ? (
              <div style={{ padding: '6px 4px' }}><Sans s={11} c={C.dim}>{ru ? 'пусто' : 'empty'}</Sans></div>
            ) : (
              <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
                {sortedRows.map((r, i) => {
                  const st = getStatus ? getStatus(r) : null;
                  return (
                    <div key={i} style={{ display: 'flex', alignItems: 'center', gap: 10, padding: '8px 10px', border: `1px solid ${C.line}`, background: C.bg }}>
                      {st != null && (
                        <span title={adminStatusTitle(st, ru)} style={{
                          width: 10, height: 10, borderRadius: '50%', flexShrink: 0,
                          background: adminStatusColor(st),
                          boxShadow: `0 0 6px ${adminStatusColor(st)}88`,
                        }} />
                      )}
                      <div style={{ flex: 1, minWidth: 0 }}><Sans s={11} c={C.hi}>{render(r)}</Sans></div>
                      {rowAction && canManage && rowAction(r, { busy })}
                      {onDel && canManage && <button onClick={() => onDel(r)} disabled={busy} style={delBtn}>✕</button>}
                    </div>
                  );
                })}
              </div>
            )}
          </div>
        )}
      </div>
    );
  }

  // Модал пополнения баланса. Вызывает /api/payments/create через FURY_API.pay
  // и редиректит игрока на чекаут ЮKassa или Paypalych. Никаких локальных списаний —
  // деньги начисляются только когда придёт webhook от провайдера.
  // Сервисный сбор, который игрок платит поверх суммы пакета (синхрон c .env PAYMENT_COMMISSION_PERCENT).
  // Покрывает комиссию платёжной системы и налог НПД — на баланс зачисляется ровно «Сумма».
  const PAYMENT_COMMISSION_PCT = 12;

  function TopUpModal({ lang, onClose, loggedIn }) {
    const [amount, setAmount] = React.useState(500);
    const [email, setEmail]   = React.useState('');
    const [step, setStep]     = React.useState('form');
    const [order, setOrder]   = React.useState(null);
    const [busy, setBusy]     = React.useState(false);
    const [err, setErr]       = React.useState(null);
    const presets = [100, 250, 500, 1000, 2500, 5000];
    const ru = lang === 'ru';
    const T = ru
      ? { title: 'Пополнение баланса', amount: 'Сумма', rub: '₽',
          pay: 'Перейти к оплате', cancel: 'Отмена', back: 'Назад', confirm: 'Я оплатил',
          login: 'Сначала войдите через Steam',
          email: 'Email для чека', emailHint: 'На него придёт официальный чек ФНС об оплате',
          emailInvalid: 'Укажите корректный email',
          qrTitle: 'Оплата по QR-коду Сбербанка',
          qrHelp: 'Откройте приложение Сбербанк → сканируйте QR-код или переведите по номеру телефона. Сумма к переводу указана ниже.',
          recipient: 'Получатель', recipientPhone: 'Телефон получателя', recipientCard: 'Карта получателя',
          orderId: 'Номер заказа',
          afterPay: 'После оплаты нажмите «Я оплатил» — на email придёт чек ФНС, баланс пополнится автоматически.',
          done: 'Оплата принята', doneDesc: 'Чек ФНС отправлен на email. Баланс пополнен.', close: 'Закрыть',
          verifyTitle: 'Ожидаем подтверждение от ФНС…', verifyDesc: 'Как только Сбербанк зарегистрирует ваш перевод в ФНС — мы автоматически пополним баланс и отправим чек на email. Обычно занимает 1–5 минут.',
          remain: 'Осталось',
          expired: 'Платёж не подтверждён', expiredDesc: 'За 10 минут ФНС не зарегистрировала ваш перевод. Если вы оплатили — обратитесь в поддержку с номером заказа и временем платежа.',
          retry: 'Начать заново' }
      : { title: 'Top up balance', amount: 'Amount', rub: '₽',
          pay: 'Proceed to payment', cancel: 'Cancel', back: 'Back', confirm: 'I paid',
          login: 'Please log in with Steam first',
          email: 'Email for receipt', emailHint: 'Official FNS receipt will be emailed there',
          emailInvalid: 'Please enter a valid email',
          qrTitle: 'Sberbank QR payment',
          qrHelp: 'Open the Sberbank app, scan the QR or transfer by phone. Pay exactly the amount below.',
          recipient: 'Recipient', recipientPhone: 'Phone', recipientCard: 'Card',
          orderId: 'Order ID',
          afterPay: 'After paying, press "I paid" — FNS receipt will be emailed, balance credited automatically.',
          done: 'Payment received', doneDesc: 'FNS receipt sent to your email. Balance topped up.', close: 'Close',
          verifyTitle: 'Waiting for FNS confirmation…', verifyDesc: 'As soon as Sberbank registers your transfer with the Russian tax service, we will top up your balance and send the receipt to your email. Usually 1–5 minutes.',
          remain: 'Remaining',
          expired: 'Payment not confirmed', expiredDesc: 'FNS did not register your transfer within 10 minutes. If you have paid, contact support with your order number and payment time.',
          retry: 'Start over' };

    async function goPay() {
      if (!loggedIn) { setErr(T.login); return; }
      const re = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
      const e = email.trim().toLowerCase();
      if (!re.test(e)) { setErr(T.emailInvalid); return; }
      const a = Math.max(1, Math.floor(Number(amount) || 0));
      if (!a) { setErr('bad_amount'); return; }
      setBusy(true); setErr(null);
      try {
        const res = await window.FURY_API.initPayment(a, e);
        setOrder(res); setStep('qr');
      } catch (ex) {
        const code = ex && ex.data && ex.data.error;
        if (code === 'too_many_pending') {
          setErr(ru
            ? `Слишком много активных заказов (${ex.data.limit}). Сначала отмените или дождитесь экспайра.`
            : `Too many active orders (${ex.data.limit}). Cancel or wait for them to expire first.`);
        } else {
          setErr(code || (ex.message || 'init_failed'));
        }
      } finally { setBusy(false); }
    }

    // При закрытии диалога на этапе qr/verifying — отменяем pending-заказ,
    // освобождая копеечный слот. Это критично против DoS копеечного пула.
    async function cancelOrderQuietly() {
      if (!order || !order.order_id) return;
      try { await window.FURY_API.cancelPayment(order.order_id); }
      catch (_) { /* swallow — лимит/гонка, не критично для UX */ }
    }
    const isLiveStep = step === 'qr' || step === 'verifying';
    async function closeAndCancel() {
      if (isLiveStep) await cancelOrderQuietly();
      onClose();
    }
    async function backToForm() {
      if (isLiveStep) await cancelOrderQuietly();
      setStep('form'); setOrder(null); setErr(null);
    }

    // ESC закрывает диалог (с авто-отменой заказа на live-этапе).
    React.useEffect(() => {
      const onKey = (e) => { if (e.key === 'Escape') closeAndCancel(); };
      window.addEventListener('keydown', onKey);
      return () => window.removeEventListener('keydown', onKey);
    }, [step, order && order.order_id]);

    async function confirmPaid() {
      if (!order) return;
      setBusy(true); setErr(null);
      try {
        const res = await window.FURY_API.confirmPayment(order.order_id);
        if (res.status === 'paid') { setStep('done'); if (window.dispatchEvent) window.dispatchEvent(new CustomEvent('fury:refresh-profile')); }
        else { setStep('verifying'); }
      } catch (ex) {
        setErr(ex && ex.data && ex.data.error ? ex.data.error : (ex.message || 'confirm_failed'));
      } finally { setBusy(false); }
    }

    // Polling-эффект: пока в состоянии 'verifying' — раз в 4 сек дёргаем order-status.
    React.useEffect(() => {
      if (step !== 'verifying' || !order) return;
      let alive = true;
      const tick = async () => {
        if (!alive) return;
        try {
          const s = await window.FURY_API.orderStatus(order.order_id);
          if (!alive) return;
          if (s.status === 'paid') { setStep('done'); if (window.dispatchEvent) window.dispatchEvent(new CustomEvent('fury:refresh-profile')); return; }
          if (s.status === 'expired') { setStep('expired'); return; }
          setOrder((o) => o ? ({ ...o, remain_sec: s.remain_sec, age_sec: s.age_sec }) : o);
        } catch (e) { /* swallow; will retry */ }
      };
      tick();
      const ivPoll = setInterval(tick, 4000);
      // Локальный 1-сек tick: уменьшает remain_sec на 1 каждую секунду между пуллами — чтобы таймер шёл плавно.
      const ivLocal = setInterval(() => {
        setOrder((o) => (o && typeof o.remain_sec === 'number' && o.remain_sec > 0) ? ({ ...o, remain_sec: o.remain_sec - 1 }) : o);
      }, 1000);
      return () => { alive = false; clearInterval(ivPoll); clearInterval(ivLocal); };
    }, [step, order && order.order_id]);

    const overlay = { position: 'fixed', inset: 0, background: 'rgba(13,10,8,0.85)', zIndex: 1100, display: 'flex', alignItems: 'center', justifyContent: 'center', padding: 20 };
    const box = { background: C.bg, border: '1px solid ' + C.line, width: 'min(520px, 100%)', maxHeight: '92vh', overflow: 'auto', padding: 30 };
    const xBtn = { background: 'transparent', border: 'none', cursor: 'pointer', color: C.dim, fontSize: 20 };

    if (step === 'verifying') {
      const r = order && order.remain_sec != null ? order.remain_sec : null;
      const mm = r != null ? String(Math.floor(r / 60)).padStart(1, '0') : '—';
      const ss = r != null ? String(r % 60).padStart(2, '0') : '—';
      return (
        <div style={overlay}>
          <div style={box}>
            <div style={{ display: 'flex', alignItems: 'center', marginBottom: 24 }}>
              <Display s={20} w={500}>{T.verifyTitle}</Display>
              <div style={{ flex: 1 }} />
              <button onClick={closeAndCancel} style={xBtn}>×</button>
            </div>
            <div style={{ textAlign: 'center', padding: '32px 0' }}>
              <div style={{
                width: 64, height: 64, margin: '0 auto 18px', border: `3px solid ${C.line}`,
                borderTopColor: C.ember, borderRadius: '50%',
                animation: 'fury-spin 1s linear infinite',
              }} />
              <Display s={36} c={C.hi} w={500} style={{ fontFamily: 'monospace', letterSpacing: '0.05em' }}>{mm}:{ss}</Display>
              <Sans s={9} c={C.dim} upper tracked style={{ marginTop: 6 }}>{T.remain}</Sans>
            </div>
            <Sans s={12} c={C.text} style={{ lineHeight: 1.5, marginBottom: 18, textAlign: 'center' }}>{T.verifyDesc}</Sans>
            {order && (
              <div style={{ background: 'rgba(13,10,8,0.5)', padding: 12, marginBottom: 18, border: '1px solid ' + C.lineSoft }}>
                <Sans s={10} c={C.dim} style={{ fontFamily: 'monospace' }}>{T.orderId}: {order.order_id.slice(0, 8)} · {T.amount}: {order.amount} {T.rub}</Sans>
              </div>
            )}
            <div style={{ display: 'flex', justifyContent: 'flex-end' }}>
              <Btn kind="ghost" onClick={closeAndCancel}>{T.cancel}</Btn>
            </div>
            <style>{`@keyframes fury-spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }`}</style>
          </div>
        </div>
      );
    }

    if (step === 'expired') {
      return (
        <div style={overlay}>
          <div style={box}>
            <div style={{ display: 'flex', alignItems: 'center', marginBottom: 24 }}>
              <Display s={20} w={500}>{T.expired}</Display>
              <div style={{ flex: 1 }} />
              <button onClick={onClose} style={xBtn}>×</button>
            </div>
            <div style={{ padding: '20px 0', textAlign: 'center' }}>
              <div style={{ fontSize: 42, marginBottom: 12 }}>⏱️</div>
              <Sans s={13} c={C.hi} style={{ lineHeight: 1.5 }}>{T.expiredDesc}</Sans>
            </div>
            <div style={{ display: 'flex', justifyContent: 'space-between', gap: 10 }}>
              <Btn kind="ghost" onClick={onClose}>{T.close}</Btn>
              <Btn kind="blood" onClick={() => { setStep('form'); setOrder(null); setErr(null); }}>{T.retry}</Btn>
            </div>
          </div>
        </div>
      );
    }

    if (step === 'done') {
      return (
        <div style={overlay}>
          <div style={box}>
            <div style={{ display: 'flex', alignItems: 'center', marginBottom: 24 }}>
              <Display s={22} w={500}>{T.done}</Display>
              <div style={{ flex: 1 }} />
              <button onClick={onClose} style={xBtn}>×</button>
            </div>
            <div style={{ padding: '32px 0', textAlign: 'center' }}>
              <div style={{ fontSize: 48, marginBottom: 12 }}>✅</div>
              <Sans s={14} c={C.hi}>{T.doneDesc}</Sans>
            </div>
            <div style={{ display: 'flex', justifyContent: 'flex-end' }}>
              <Btn kind="ghost" onClick={onClose}>{T.close}</Btn>
            </div>
          </div>
        </div>
      );
    }

    if (step === 'qr' && order) {
      return (
        <div style={overlay}>
          <div style={box}>
            <div style={{ display: 'flex', alignItems: 'center', marginBottom: 18 }}>
              <Display s={20} w={500}>{T.qrTitle}</Display>
              <div style={{ flex: 1 }} />
              <button onClick={closeAndCancel} style={xBtn}>×</button>
            </div>
            <Sans s={12} c={C.text} style={{ lineHeight: 1.5, marginBottom: 18 }}>{T.qrHelp}</Sans>

            <div style={{ background: 'rgba(200,155,79,0.08)', border: '1px solid ' + C.ember, padding: 18, marginBottom: 18, textAlign: 'center' }}>
              <Sans s={9} c={C.dim} upper tracked>{ru ? 'К оплате — ровно эта сумма' : 'Pay exactly this amount'}</Sans>
              <Display s={42} c={C.hi} w={600} style={{ marginTop: 8, fontFamily: 'monospace', letterSpacing: '0.04em' }}>
                {order.amount.toFixed(2)} ₽
              </Display>
              <button onClick={() => {
                try { navigator.clipboard.writeText(order.amount.toFixed(2)); setErr(ru ? 'Сумма скопирована' : 'Amount copied'); setTimeout(() => setErr(null), 1500); } catch (_) {}
              }} style={{
                marginTop: 10, background: 'transparent', border: '1px solid ' + C.ember, color: C.ember,
                padding: '6px 16px', cursor: 'pointer', fontFamily: F.sans, fontSize: 11,
                letterSpacing: '0.14em', textTransform: 'uppercase',
              }}>{ru ? 'Скопировать сумму' : 'Copy amount'}</button>
              <div style={{ marginTop: 10 }}><Sans s={10} c={C.dim}>{ru ? 'Эта сумма зачислится на ваш баланс. Копейки уникальны — чтобы сматчить именно ваш платёж.' : 'This exact amount will be credited to your balance. The kopecks are unique to identify your specific payment.'}</Sans></div>
            </div>

            <div style={{ background: '#fff', padding: 18, textAlign: 'center', marginBottom: 18 }}>
              <img src={order.recipient.qr_url} alt="QR"
                   style={{ maxWidth: 240, width: '100%', display: 'block', margin: '0 auto' }}
                   onError={(e) => { e.target.style.display = 'none'; if (e.target.nextSibling) e.target.nextSibling.style.display = 'block'; }} />
              <div style={{ display: 'none', color: '#0d0a08', padding: 20, fontSize: 12 }}>
                {ru ? 'QR-код не загружен — используйте перевод по номеру телефона.' : 'QR not uploaded yet — use phone transfer.'}
              </div>
            </div>

            <table style={{ width: '100%', borderCollapse: 'collapse', marginBottom: 18 }}>
              <tbody>
                <tr>
                  <td style={{ padding: '8px 0', borderBottom: '1px solid ' + C.line }}>
                    <Sans s={9} c={C.dim} upper tracked>{T.recipient}</Sans>
                  </td>
                  <td style={{ padding: '8px 0', borderBottom: '1px solid ' + C.line, textAlign: 'right' }}>
                    <Sans s={12} c={C.text}>{order.recipient.name}</Sans>
                  </td>
                </tr>
                <tr>
                  <td style={{ padding: '8px 0', borderBottom: '1px solid ' + C.line }}>
                    <Sans s={9} c={C.dim} upper tracked>{T.recipientCard}</Sans>
                  </td>
                  <td style={{ padding: '8px 0', borderBottom: '1px solid ' + C.line, textAlign: 'right' }}>
                    <Sans s={12} c={C.text} style={{ fontFamily: 'monospace' }}>**** {order.recipient.card_tail}</Sans>
                  </td>
                </tr>
                <tr>
                  <td style={{ padding: '8px 0' }}>
                    <Sans s={9} c={C.dim} upper tracked>{T.orderId}</Sans>
                  </td>
                  <td style={{ padding: '8px 0', textAlign: 'right' }}>
                    <Sans s={10} c={C.dim} style={{ fontFamily: 'monospace' }}>{order.order_id.slice(0, 8)}</Sans>
                  </td>
                </tr>
              </tbody>
            </table>

            <Sans s={11} c={C.dim} style={{ lineHeight: 1.5, marginBottom: 18 }}>{T.afterPay}</Sans>

            {err && (
              <div style={{ background: 'rgba(196,58,58,0.15)', border: '1px solid ' + C.blood, padding: 12, marginBottom: 14 }}>
                <Sans s={12} c={C.hi}>{err}</Sans>
              </div>
            )}

            <div style={{ display: 'flex', gap: 10, justifyContent: 'space-between', flexWrap: 'wrap' }}>
              <Btn kind="ghost" onClick={closeAndCancel} disabled={busy}>{T.cancel}</Btn>
              <div style={{ display: 'flex', gap: 10 }}>
                <Btn kind="ghost" onClick={backToForm} disabled={busy}>{T.back}</Btn>
                <Btn kind="blood" onClick={confirmPaid} disabled={busy}>{busy ? '…' : T.confirm}</Btn>
              </div>
            </div>
          </div>
        </div>
      );
    }

    return (
      <div style={overlay}>
        <div style={box}>
          <div style={{ display: 'flex', alignItems: 'center', marginBottom: 24 }}>
            <Display s={22} w={500}>{T.title}</Display>
            <div style={{ flex: 1 }} />
            <button onClick={onClose} style={xBtn}>×</button>
          </div>

          <div>
            <Sans s={9} c={C.dim} upper tracked>{T.amount}</Sans>
            <div style={{ marginTop: 8, display: 'flex', gap: 6, flexWrap: 'wrap' }}>
              {presets.map((v) => (
                <button key={v} onClick={() => setAmount(v)} style={{
                  flex: 1, background: amount === v ? C.ember : 'transparent',
                  color: amount === v ? C.bg : C.hi,
                  border: '1px solid ' + (amount === v ? C.ember : C.line),
                  padding: '10px 0', cursor: 'pointer',
                  fontFamily: F.sans, fontSize: 12, letterSpacing: '0.1em',
                  textTransform: 'uppercase', fontWeight: 600, minWidth: 56,
                }}>{v}</button>
              ))}
            </div>
            <input type="number" value={amount} onChange={(e) => setAmount(e.target.value)}
                   style={{ marginTop: 10, width: '100%', boxSizing: 'border-box',
                            background: 'transparent', border: '1px solid ' + C.line,
                            padding: '12px 14px', color: C.hi, fontFamily: F.sans, fontSize: 14 }} />
          </div>

          <div style={{ marginTop: 18 }}>
            <Sans s={9} c={C.dim} upper tracked>{T.email}</Sans>
            <input type="email" value={email} onChange={(e) => setEmail(e.target.value)}
                   placeholder="your@email.com" autoComplete="email"
                   style={{ marginTop: 8, width: '100%', boxSizing: 'border-box',
                            background: 'transparent', border: '1px solid ' + C.line,
                            padding: '12px 14px', color: C.hi, fontFamily: F.sans,
                            fontSize: 14, letterSpacing: '0.04em' }} />
            <Sans s={10} c={C.dim} style={{ marginTop: 6 }}>{T.emailHint}</Sans>
          </div>

          {err && (
            <div style={{ background: 'rgba(196,58,58,0.15)', border: '1px solid ' + C.blood,
                          padding: 12, marginTop: 16 }}>
              <Sans s={12} c={C.hi}>{err}</Sans>
            </div>
          )}

          <div style={{ display: 'flex', gap: 10, justifyContent: 'flex-end', marginTop: 24 }}>
            <Btn kind="ghost" onClick={onClose} disabled={busy}>{T.cancel}</Btn>
            <Btn kind="blood" onClick={goPay} disabled={busy || !email.trim()}>{busy ? '…' : T.pay}</Btn>
          </div>
        </div>
      </div>
    );
  }


  // ── Social icon (clickable, opens new tab). Disabled-style if URL is empty.
  function FurySocialIcon({ kind, url }) {
    const enabled = !!(url && /^https?:\/\//i.test(url));
    const BRAND = {
      discord: '#5865F2', vk: '#0077FF', tg: '#229ED9', youtube: '#FF0000', steam: '#66C0F4',
    };
    const PATHS = {
      discord: 'M19.27 5.33A17.32 17.32 0 0 0 14.97 4l-.22.42a16.5 16.5 0 0 1 4.05 1.3 13.2 13.2 0 0 0-4.85-1.43 13.2 13.2 0 0 0-4.85 1.43 16.5 16.5 0 0 1 4.05-1.3L12.93 4a17.32 17.32 0 0 0-4.3 1.33C5.96 9.34 5.24 13.25 5.6 17.1a17.6 17.6 0 0 0 5.35 2.7l1.07-1.46a11.2 11.2 0 0 1-2.79-1.34l.23-.16a12.3 12.3 0 0 0 10.98 0l.23.16a11.2 11.2 0 0 1-2.79 1.34l1.07 1.46a17.6 17.6 0 0 0 5.35-2.7c.42-4.45-.7-8.32-3.03-11.77ZM10.5 14.7c-1.05 0-1.91-.97-1.91-2.16 0-1.2.84-2.17 1.91-2.17 1.08 0 1.93.98 1.91 2.17 0 1.2-.84 2.16-1.91 2.16Zm6.9 0c-1.06 0-1.91-.97-1.91-2.16 0-1.2.84-2.17 1.91-2.17 1.08 0 1.93.98 1.91 2.17 0 1.2-.83 2.16-1.91 2.16Z',
      vk:      'M23.92 7.34c.17-.55 0-.96-.81-.96h-2.66c-.69 0-1 .35-1.17.74 0 0-1.36 3.27-3.28 5.39-.62.61-.91.81-1.25.81-.17 0-.42-.2-.42-.74V7.34c0-.66-.2-.96-.77-.96H9.42c-.43 0-.69.31-.69.6 0 .63.95.78 1.04 2.55v3.85c0 .84-.15.99-.5.99-.91 0-3.11-3.29-4.42-7.06-.26-.72-.52-1-1.22-1H.97c-.78 0-.93.35-.93.74 0 .69 1 4.13 4.3 8.68 2.21 3.11 5.32 4.8 8.16 4.8 1.7 0 1.91-.37 1.91-1v-2.32c0-.74.16-.89.7-.89.4 0 1.08.2 2.67 1.71 1.82 1.79 2.13 2.59 3.15 2.59h2.66c.78 0 1.18-.37.95-1.1-.25-.74-1.18-1.81-2.43-3.07-.68-.79-1.69-1.65-2-2.08-.43-.55-.31-.79 0-1.29 0 0 3.56-4.95 3.94-6.63Z',
      tg:      'M21.94 4.07 2.5 11.66c-1.33.53-1.32 1.27-.24 1.6l4.99 1.56 1.94 5.92c.24.66.34.93.95.93.46 0 .65-.21.91-.46l2.3-2.23 4.78 3.54c.88.49 1.52.23 1.74-.82l3.15-14.85c.32-1.27-.5-1.86-1.34-1.48Z',
      steam:   'M11.979 0C5.678 0 .511 4.86.022 11.037l6.432 2.658c.545-.371 1.203-.59 1.912-.59.063 0 .125.004.188.006l2.861-4.142V8.91c0-2.495 2.028-4.524 4.524-4.524 2.494 0 4.524 2.031 4.524 4.527s-2.03 4.525-4.524 4.525h-.105l-4.076 2.911c0 .052.004.105.004.159 0 1.875-1.515 3.396-3.39 3.396-1.635 0-3.016-1.173-3.331-2.727L.436 15.27C1.862 20.307 6.486 24 11.979 24c6.627 0 11.999-5.373 11.999-12S18.605 0 11.979 0zM7.54 18.21l-1.473-.61c.262.543.714.999 1.314 1.25 1.297.539 2.793-.076 3.332-1.375.263-.63.264-1.319.005-1.949s-.75-1.121-1.377-1.383c-.624-.26-1.29-.249-1.878-.03l1.523.63c.956.4 1.409 1.5 1.009 2.455-.397.957-1.497 1.41-2.454 1.012H7.54zm11.415-9.303c0-1.662-1.353-3.015-3.015-3.015-1.665 0-3.015 1.353-3.015 3.015 0 1.665 1.35 3.015 3.015 3.015 1.663 0 3.015-1.35 3.015-3.015zm-5.273-.005c0-1.252 1.013-2.266 2.265-2.266 1.249 0 2.266 1.014 2.266 2.266 0 1.251-1.017 2.265-2.266 2.265-1.253 0-2.265-1.014-2.265-2.265z',
      youtube: 'M23.5 7.2a3 3 0 0 0-2.1-2.1C19.5 4.5 12 4.5 12 4.5s-7.5 0-9.4.6A3 3 0 0 0 .5 7.2C0 9.1 0 12 0 12s0 2.9.5 4.8a3 3 0 0 0 2.1 2.1c1.9.6 9.4.6 9.4.6s7.5 0 9.4-.6a3 3 0 0 0 2.1-2.1c.5-1.9.5-4.8.5-4.8s0-2.9-.5-4.8ZM9.6 15.6V8.4l6.3 3.6-6.3 3.6Z',
    };
    const isSteam = kind === 'steam';
    const baseStyle = {
      display: 'flex', alignItems: 'center', justifyContent: 'center',
      width: 36, height: 36,
      border: `1px solid ${C.lineSoft}`,
      background: 'rgba(13,10,8,0.65)',
      color: enabled ? (isSteam ? '#ffffff' : BRAND[kind]) : C.dim,
      opacity: enabled ? 1 : 0.35,
      cursor: enabled ? 'pointer' : 'not-allowed',
      transition: 'all .15s', textDecoration: 'none',
    };
    const content = (
      <svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
        <path d={PATHS[kind]} />
      </svg>
    );
    if (!enabled) return <div style={baseStyle} title={kind.toUpperCase() + ' — link not set'}>{content}</div>;
    return (
      <a href={url} target="_blank" rel="noopener noreferrer" style={baseStyle}
         onMouseEnter={(e) => { e.currentTarget.style.borderColor = isSteam ? '#ffffff' : BRAND[kind]; }}
         onMouseLeave={(e) => { e.currentTarget.style.borderColor = C.lineSoft; }}>
        {content}
      </a>
    );
  }



  // ── Owner-only: drag-and-drop reordering of products via window.FURY_PRODUCTS ─
  function reorderProducts(fromId, toId) {
    const list = (window.FURY_PRODUCTS || []).slice();
    const fromIdx = list.findIndex((p) => p.id === fromId);
    const toIdx   = list.findIndex((p) => p.id === toId);
    if (fromIdx < 0 || toIdx < 0 || fromIdx === toIdx) return null;
    const [moved] = list.splice(fromIdx, 1);
    list.splice(toIdx, 0, moved);
    return list;
  }
  async function saveReorder(newList) {
    const ids = newList.map((p) => p.id);
    try {
      const r = await fetch('/api/owner/products/reorder', {
        method: 'POST', credentials: 'include',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ order: ids }),
      });
      if (!r.ok) throw new Error('http_' + r.status);
      window.FURY_PRODUCTS = newList;
      window.__furyDataVersion = (window.__furyDataVersion || 0) + 1;
      window.dispatchEvent(new CustomEvent('fury:data'));
      window.dispatchEvent(new CustomEvent('fury:products-changed'));
      return true;
    } catch (e) {
      console.error('[fury] saveReorder failed', e);
      return false;
    }
  }

  // ── Viewport size hook. Triggers rerender when window resizes.
  function useViewport() {
    const [w, setW] = React.useState(typeof window !== 'undefined' ? window.innerWidth : 1280);
    React.useEffect(() => {
      const f = () => setW(window.innerWidth);
      window.addEventListener('resize', f);
      return () => window.removeEventListener('resize', f);
    }, []);
    return { w, isMobile: w < 720, isTablet: w >= 720 && w < 1080, isDesktop: w >= 1080 };
  }

  // ── Настройки сайта (футер + политики + лого/фавикон). Кешим в window.
  // Любая смена настроек прокатывается через событие 'fury:site-settings'.
  function useSiteSettings() {
    const [s, setS] = React.useState(window.__FURY_SITE_SETTINGS || null);
    React.useEffect(() => {
      let alive = true;
      function load() {
        fetch('/api/site-settings', { credentials: 'include' })
          .then((r) => r.json())
          .then((d) => {
            if (!alive) return;
            window.__FURY_SITE_SETTINGS = d.settings;
            applyFavicon(d.settings.favicon_url);
            setS(d.settings);
          })
          .catch(() => {});
      }
      if (!s) load();
      function onChange() { load(); }
      window.addEventListener('fury:site-settings', onChange);
      return () => { alive = false; window.removeEventListener('fury:site-settings', onChange); };
    }, []);
    return s;
  }

  // Динамически вставляет/обновляет <link rel="icon"> в <head>.
  function applyFavicon(url) {
    let link = document.querySelector('link[rel~="icon"]');
    if (!url) {
      if (link) link.remove();
      return;
    }
    if (!link) {
      link = document.createElement('link');
      link.rel = 'icon';
      document.head.appendChild(link);
    }
    link.href = url + (url.includes('?') ? '&' : '?') + 'v=' + Date.now();  // bust cache
  }

  // ── Подвал сайта с Discord/политиками/модом + просмотром политик в модале.
  function SiteFooter({ lang }) {
    const settings = useSiteSettings();
    const [policy, setPolicy] = React.useState(null);  // { title, text }
    const ru = lang === 'ru';
    if (!settings) return null;
    const labels = ru ? {
      terms:      'Пользовательское соглашение',
      privacy:    'Политика конфиденциальности',
      security:   'Политика инф. безопасности',
      requisites: 'Реквизиты',
      discord:    'Discord',
      mod:        'Мод в Steam',
    } : {
      terms:      'Terms of Use',
      privacy:    'Privacy Policy',
      security:   'Information Security Policy',
      requisites: 'Legal info',
      discord:    'Discord',
      mod:        'Steam Workshop',
    };
    const link = (label, href) => (
      <a href={href} target="_blank" rel="noopener noreferrer" style={{
        color: C.dim, textDecoration: 'none', fontFamily: F.sans, fontSize: 11,
        letterSpacing: '0.18em', textTransform: 'uppercase',
      }}>{label}</a>
    );
    const policyBtn = (label, key, title) => (
      <button onClick={() => setPolicy({ title, text: settings[key] || '' })} style={{
        background: 'transparent', border: 'none', cursor: 'pointer', color: C.dim,
        fontFamily: F.sans, fontSize: 11, letterSpacing: '0.18em', textTransform: 'uppercase', padding: 0,
      }}>{label}</button>
    );
    return (
      <>
        <footer style={{
          padding: '32px 48px', borderTop: `1px solid ${C.lineSoft}`,
          display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: 20, flexWrap: 'wrap',
        }}>
          <Sans s={10} c={C.dim} upper tracked>FURY · DayZ · {new Date().getFullYear()}</Sans>
          <div style={{ display: 'flex', gap: 20, flexWrap: 'wrap', alignItems: 'center' }}>
            {link(labels.terms,      '/oferta')}
            {link(labels.privacy,    '/privacy')}
            {link(labels.requisites, '/requisites')}
            {link(labels.security, '/security')}
            {settings.discord_url && link(labels.discord, settings.discord_url)}
            {settings.mod_url     && link(labels.mod,     settings.mod_url)}
          </div>
        </footer>
        {policy && <PolicyModal lang={lang} title={policy.title} text={policy.text} onClose={() => setPolicy(null)} />}
      </>
    );
  }

  function PolicyModal({ lang, title, text, onClose }) {
    return (
      <div onClick={onClose} style={{
        position: 'fixed', inset: 0, zIndex: 1100, background: 'rgba(13,10,8,0.82)',
        display: 'flex', alignItems: 'center', justifyContent: 'center',
      }}>
        <div onClick={(e) => e.stopPropagation()} style={{
          background: C.bg, border: `1px solid ${C.line}`,
          width: 'min(720px, 92vw)', maxHeight: '82vh', display: 'flex', flexDirection: 'column',
        }}>
          <div style={{ padding: '20px 26px', borderBottom: `1px solid ${C.line}`, display: 'flex', alignItems: 'center' }}>
            <Display s={22} italic w={500}>{title}</Display>
            <div style={{ flex: 1 }} />
            <button onClick={onClose} style={{
              background: 'transparent', border: 'none', cursor: 'pointer', color: C.dim,
              fontSize: 20, padding: '0 6px',
            }}>×</button>
          </div>
          <div style={{ padding: '20px 26px', overflow: 'auto', whiteSpace: 'pre-wrap' }}>
            <Sans s={13} c={C.text} style={{ lineHeight: 1.65 }}>{text || (lang === 'ru' ? '(пусто)' : '(empty)')}</Sans>
          </div>
        </div>
      </div>
    );
  }

  // ── Маленький REST-помощник для админ-API. credentials:'include' — куки сессии. ─
  async function api(path, opts = {}) {
    const base = (window.FURY_API && window.FURY_API.base) || '';
    const r = await fetch(base + path, {
      credentials: 'include',
      headers: { 'Content-Type': 'application/json' },
      ...opts,
    });
    const data = await r.json().catch(() => ({}));
    if (!r.ok) throw Object.assign(new Error(data.error || `${path} → ${r.status}`), { status: r.status, data });
    return data;
  }

  // ── Бонусный код: ввод в профиле, шлёт POST /api/me/bonus-codes/redeem
  function BonusCodeRedeem({ lang }) {
    const [code, setCode] = React.useState('');
    const [busy, setBusy] = React.useState(false);
    const [msg, setMsg]   = React.useState(null);
    const ru = lang === 'ru';

    async function submit() {
      if (!code.trim()) return;
      setBusy(true); setMsg(null);
      try {
        const r = await api('/api/me/bonus-codes/redeem', {
          method: 'POST',
          body: JSON.stringify({ code: code.trim().toUpperCase() }),
        });
        setMsg({ kind: 'ok', text: ru
          ? `Начислено ${r.amount} ₽ · баланс ${r.balance} ₽`
          : `Credited ${r.amount} ₽ · balance ${r.balance} ₽` });
        setCode('');
        if (window.FURY_API && window.FURY_API.refreshMe) window.FURY_API.refreshMe();
      } catch (e) {
        const errMap = ru ? {
          not_found: 'Код не найден',
          revoked: 'Код отозван владельцем',
          expired: 'Срок действия истёк',
          exhausted: 'Лимит использований исчерпан',
          already_redeemed: 'Этот код уже активирован',
        } : {
          not_found: 'Code not found',
          revoked: 'Code revoked by owner',
          expired: 'Code expired',
          exhausted: 'Usage limit exhausted',
          already_redeemed: 'You have already used this code',
        };
        setMsg({ kind: 'err', text: errMap[e.data?.error] || e.message });
      } finally { setBusy(false); }
    }

    return (
      <div style={{ marginTop: 32, padding: 22, background: C.panel2, border: `1px solid ${C.line}` }}>
        <Mono c={C.ember}>· {ru ? 'БОНУСНЫЙ КОД' : 'BONUS CODE'}</Mono>
        <div style={{ marginTop: 8 }}>
          <Sans s={11} c={C.dim}>{ru
            ? 'Введите код, выданный администратором FURY. Каждый код действует один раз на аккаунт и не более 24 часов.'
            : 'Enter a code issued by a FURY admin. Each code is one-time per account, valid up to 24 hours.'}</Sans>
        </div>
        <div style={{ marginTop: 14, display: 'flex', gap: 10 }}>
          <input
            value={code}
            onChange={(e) => setCode(e.target.value.toUpperCase())}
            onKeyDown={(e) => { if (e.key === 'Enter') submit(); }}
            placeholder="XXXX-XXXX-XXXX"
            style={{
              flex: 1, background: 'transparent', border: `1px solid ${C.line}`,
              color: C.hi, padding: '10px 14px',
              fontFamily: F.mono, fontSize: 14, letterSpacing: '0.18em', textTransform: 'uppercase',
            }} />
          <Btn kind="blood" onClick={submit} disabled={busy || !code.trim()}>
            {busy ? '…' : (ru ? 'Активировать' : 'Redeem')}
          </Btn>
        </div>
        {msg && (
          <div style={{
            marginTop: 12, padding: '8px 12px',
            background: msg.kind === 'ok' ? 'rgba(60,140,60,0.15)' : 'rgba(150,30,30,0.18)',
            border: `1px solid ${msg.kind === 'ok' ? '#3c8c3c' : C.blood}`,
          }}>
            <Sans s={11} c={C.paper}>{msg.text}</Sans>
          </div>
        )}
      </div>
    );
  }

  // ── Owner: управление профилем любого пользователя по SteamID ──────────
  function AdminUsers({ ru, canManage }) {
    const [sid, setSid] = React.useState('');
    const [data, setData] = React.useState(null);
    const [busy, setBusy] = React.useState(false);
    const [msg, setMsg] = React.useState(null);
    const [bal, setBal] = React.useState('');
    const [bon, setBon] = React.useState('');
    const [grant, setGrant] = React.useState({ server_id: '', days: 30 });

    const inp  = { marginTop: 4, width: 220, background: 'transparent', border: `1px solid ${C.line}`, color: C.hi, padding: '8px 10px', fontFamily: F.sans, fontSize: 12, boxSizing: 'border-box' };
    const inpS = { marginTop: 4, width: 110, background: 'transparent', border: `1px solid ${C.line}`, color: C.hi, padding: '8px 10px', fontFamily: F.sans, fontSize: 12, boxSizing: 'border-box' };
    const sel  = { background: 'transparent', border: `1px solid ${C.line}`, color: C.hi, padding: '8px 10px', fontFamily: F.sans, fontSize: 12, cursor: 'pointer' };
    const fmtD = (ms) => ms ? new Date(ms).toLocaleString(ru ? 'ru-RU' : 'en-US') : '—';
    const pn = (r) => ru ? r.name_ru : r.name_en;
    const fmtMoney = (n) => {
      if (n == null || n === '' || !Number.isFinite(Number(n))) return '—';
      const num = Number(n);
      const hasFrac = Math.abs(num - Math.trunc(num)) > 1e-9;
      const s = hasFrac ? num.toFixed(2) : String(Math.trunc(num));
      return s.replace(/\B(?=(\d{3})+(?!\d))/g, ' ');
    };
    const curSym = (c) => {
      const k = String(c || 'RUB').toUpperCase();
      return k === 'RUB' ? '₽' : k === 'USD' ? '$' : k === 'EUR' ? '€' : ' ' + k;
    };
    const statusColor = (st) => adminStatusColor(st);
    const statusTitle = (st) => adminStatusTitle(st, ru);

    async function load(idArg) {
      const q = String(idArg || sid).trim();
      if (!/^7656119\d{10}$/.test(q)) { setMsg(ru ? 'Неверный SteamID64' : 'Invalid SteamID64'); return; }
      setBusy(true); setMsg(null);
      try {
        const d = await api('/api/owner/users/' + q);
        setData(d);
        setBal(d.user ? String(d.user.balance) : '');
        setBon(d.user ? String(d.user.bonus) : '');
        setGrant((g) => ({ ...g, server_id: g.server_id || (d.servers && d.servers[0] && d.servers[0].id) || '' }));
      } catch (e) { setData(null); setMsg((e && e.message) || 'error'); }
      finally { setBusy(false); }
    }
    async function act(method, path, body) {
      setBusy(true); setMsg(null);
      try {
        await api(path, { method, body: body ? JSON.stringify(body) : undefined });
        await load(data.steam_id);
        setMsg(ru ? 'Готово' : 'Done');
      } catch (e) { setMsg((e && e.message) || 'error'); setBusy(false); }
    }

    const u = data && data.user;
    const base = data ? ('/api/owner/users/' + data.steam_id) : '';

    return (
      <div>
        <div style={{ display: 'flex', gap: 10, alignItems: 'flex-end', flexWrap: 'wrap' }}>
          <div>
            <Sans s={10} c={C.dim} upper tracked>SteamID64</Sans>
            <input value={sid} onChange={(e) => setSid(e.target.value)} placeholder="7656119XXXXXXXXXX"
              onKeyDown={(e) => { if (e.key === 'Enter') load(); }} style={inp} />
          </div>
          <Btn kind="blood" disabled={busy} onClick={() => load()}>{ru ? 'Загрузить' : 'Load'}</Btn>
          {msg && <Sans s={11} c={C.ember}>{msg}</Sans>}
        </div>

        {data && (
          <div style={{ marginTop: 20 }}>
            <Sans s={13} c={C.hi}>
              {(u ? u.display_name : (ru ? '(нет записи users)' : '(no users row)'))} · {data.steam_id} · {data.role || 'user'}
            </Sans>
            {u && canManage && (
              <div style={{ marginTop: 10, display: 'flex', gap: 14, alignItems: 'flex-end', flexWrap: 'wrap' }}>
                <div><Sans s={10} c={C.dim} upper tracked>{ru ? 'Баланс' : 'Balance'}</Sans>
                  <input value={bal} onChange={(e) => setBal(e.target.value)} style={inpS} /></div>
                <div><Sans s={10} c={C.dim} upper tracked>{ru ? 'Бонус' : 'Bonus'}</Sans>
                  <input value={bon} onChange={(e) => setBon(e.target.value)} style={inpS} /></div>
                <Btn kind="ghost" disabled={busy}
                  onClick={() => act('PATCH', base, { balance: Number(bal), bonus: Number(bon) })}>
                  {ru ? 'Сохранить' : 'Save'}
                </Btn>
              </div>
            )}
            {u && !canManage && (
              <div style={{ marginTop: 10 }}>
                <Sans s={11} c={C.dim}>
                  {ru ? 'Баланс' : 'Balance'}: {u.balance} · {ru ? 'Бонус' : 'Bonus'}: {u.bonus} · {ru ? 'только просмотр' : 'view only'}
                </Sans>
              </div>
            )}

            {canManage && (
            <div style={{ marginTop: 18, padding: '10px 12px', border: `1px solid ${C.line}` }}>
              <Sans s={10} c={C.dim} upper tracked>{ru ? 'Выдать / продлить приоритет-слот' : 'Grant / extend priority slot'}</Sans>
              <div style={{ marginTop: 6, display: 'flex', gap: 10, alignItems: 'center', flexWrap: 'wrap' }}>
                <select value={grant.server_id} onChange={(e) => setGrant((g) => ({ ...g, server_id: e.target.value }))} style={sel}>
                  {(data.servers || []).map((s) => <option key={s.id} value={s.id} style={{ background: C.bg }}>{s.name}</option>)}
                </select>
                <input value={grant.days} onChange={(e) => setGrant((g) => ({ ...g, days: e.target.value }))} style={inpS} />
                <Sans s={10} c={C.dim}>{ru ? 'дней' : 'days'}</Sans>
                <Btn kind="ghost" disabled={busy}
                  onClick={() => act('POST', base + '/priority', { server_id: grant.server_id, days: Number(grant.days) })}>
                  {ru ? 'Выдать' : 'Grant'}
                </Btn>
              </div>
            </div>
            )}

            <AdminSection ru={ru} canManage={canManage} busy={busy} title={ru ? 'Приоритет-слоты' : 'Priority slots'} rows={data.priority_slots}
              sortable={['date']}
              getDate={(r) => r.expires_at}
              getStatus={(r) => r.active ? 'active' : 'expired'}
              render={(r) => `${r.server_id} · ${r.active ? (ru ? 'актив' : 'active') : (ru ? 'истёк' : 'expired')} · ${ru ? 'до' : 'till'} ${fmtD(r.expires_at)}`}
              onDel={(r) => act('DELETE', base + '/priority/' + encodeURIComponent(r.server_id))} />
            <AdminSection ru={ru} canManage={canManage} busy={busy} title={ru ? 'Инвентарь' : 'Inventory'} rows={data.inventory}
              sortable={['date', 'qty']}
              getDate={(r) => r.acquired_at}
              getQty={(r) => r.qty}
              render={(r) => `${pn(r)} ×${r.qty} · ${fmtD(r.acquired_at)}`}
              onDel={(r) => act('DELETE', base + '/inventory/' + r.id)} />
            <AdminSection ru={ru} canManage={canManage} busy={busy} title={ru ? 'Покупки' : 'Purchases'} rows={data.purchases}
              sortable={['date', 'amount', 'qty']}
              getAmount={(r) => r.total_price}
              getQty={(r) => r.qty}
              getStatus={(r) => r.status}
              render={(r) => `#${r.id} ${pn(r)} ×${r.qty} · ${fmtMoney(r.total_price)}₽ · ${r.server_id || '—'} · ${fmtD(r.created_at)}`}
              onDel={(r) => act('DELETE', base + '/purchases/' + r.id)} />
            <AdminSection ru={ru} canManage={canManage} busy={busy} title={ru ? 'Транзакции' : 'Transactions'} rows={data.transactions}
              sortable={['date', 'amount']}
              getAmount={(r) => r.amount}
              render={(r) => `#${r.id} ${r.kind} ${r.amount >= 0 ? '+' : ''}${fmtMoney(r.amount)} → ${fmtMoney(r.balance_after)} · ${r.note || ''} · ${fmtD(r.created_at)}`}
              onDel={(r) => act('DELETE', base + '/transactions/' + r.id)} />
            <AdminSection ru={ru} canManage={canManage} busy={busy} title={ru ? 'Подарки' : 'Gifts'} rows={data.gifts}
              sortable={['date', 'amount', 'qty']}
              getAmount={(r) => r.amount_paid}
              getQty={(r) => r.qty}
              getStatus={(r) => r.status}
              render={(r) => `#${r.id} ${r.direction === 'out' ? '→' : '←'} ${pn(r)} ×${r.qty} · ${statusTitle(r.status)} · ${fmtMoney(r.amount_paid)}₽ · ${fmtD(r.created_at)}`}
              onDel={(r) => act('DELETE', base + '/gifts/' + r.id)} />
            <AdminSection ru={ru} canManage={canManage} busy={busy} title={ru ? 'Доставки' : 'Deliveries'} rows={data.deliveries}
              sortable={['date', 'qty']}
              getQty={(r) => r.qty}
              getStatus={(r) => r.status}
              render={(r) => `#${r.id} ${pn(r)} ×${r.qty} · ${r.kind} · ${statusTitle(r.status)} · ${r.server_id || '—'} · ${fmtD(r.created_at)}`}
              onDel={(r) => act('DELETE', base + '/deliveries/' + r.id)} />
            <AdminSection ru={ru} canManage={canManage} busy={busy} title={ru ? 'Платежи' : 'Payments'} rows={data.payments}
              sortable={['date', 'amount']}
              getAmount={(r) => r.amount}
              getStatus={(r) => r.status}
              render={(r) => {
                const cs = curSym(r.currency);
                const hasBal = r.balance_after != null;
                return (
                  <span style={{ display: 'flex', flexWrap: 'wrap', gap: '4px 14px', alignItems: 'baseline' }}>
                    <span style={{ color: C.dim, fontFamily: F.mono }}>#{r.id}</span>
                    <span style={{ color: C.hi, textTransform: 'uppercase', letterSpacing: '0.08em', fontSize: 10 }}>{r.provider}</span>
                    <span style={{ color: C.ember, fontWeight: 500 }}>{fmtMoney(r.amount)}{cs}</span>
                    {hasBal && (
                      <span style={{ color: C.dim, fontSize: 10 }}>
                        {ru ? 'баланс' : 'balance'}: <span style={{ color: C.text }}>{fmtMoney(r.balance_before)}{cs}</span>
                        <span style={{ color: C.dim }}> → </span>
                        <span style={{ color: C.hi, fontWeight: 500 }}>{fmtMoney(r.balance_after)}{cs}</span>
                      </span>
                    )}
                    {r.buyer_email && (
                      <span style={{ color: C.text, fontSize: 10, fontFamily: F.mono }} title={r.buyer_email}>
                        <span style={{ color: C.dim }}>✉ </span>{r.buyer_email}
                      </span>
                    )}
                    <span style={{ color: statusColor(r.status), fontSize: 10, textTransform: 'uppercase', letterSpacing: '0.08em' }}>{statusTitle(r.status)}</span>
                    <span style={{ color: C.dim, fontSize: 10, marginLeft: 'auto' }}>{fmtD(r.created_at)}</span>
                  </span>
                );
              }}
              rowAction={(r, ctx) => r.status === 'paid' ? (
                <button onClick={() => {
                  if (!window.confirm(ru
                    ? `Возврат платежа #${r.id} на ${fmtMoney(r.amount)}₽? С баланса будет списано, чек ФНС отменён.`
                    : `Refund payment #${r.id} for ${fmtMoney(r.amount)}₽? Balance will be reversed, FNS receipt cancelled.`)) return;
                  act('POST', base + '/payments/' + r.id + '/refund', { reason: 'admin' });
                }} disabled={ctx.busy} style={{
                  background: 'transparent', border: `1px solid #4a90d9`, color: '#4a90d9',
                  cursor: 'pointer', padding: '4px 9px', fontFamily: F.sans, fontSize: 10,
                  textTransform: 'uppercase', letterSpacing: '0.08em', flexShrink: 0,
                }}>{ru ? 'Возврат' : 'Refund'}</button>
              ) : null}
              onDel={(r) => act('DELETE', base + '/payments/' + r.id)} />
          </div>
        )}
      </div>
    );
  }

  // ── Owner: управление «Сделкой дня» ──────────────────────────────────
  function AdminDailyDeal({ ru }) {
    const [data, setData] = React.useState(null);
    const [disc, setDisc] = React.useState('');
    const [pid, setPid]   = React.useState('');
    const [busy, setBusy] = React.useState(false);
    const [msg, setMsg] = React.useState(null);

    const load = React.useCallback(() => {
      api('/api/owner/daily-deal')
        .then((d) => { setData(d); if (d.deal) { setDisc(String(d.deal.discount_pct)); setPid(String(d.deal.product_id)); } })
        .catch((e) => setMsg((e && e.message) || 'error'));
    }, []);
    React.useEffect(() => { load(); }, [load]);

    async function act(fn, okMsg) {
      setBusy(true); setMsg(null);
      try {
        const r = await fn();
        if (r && r.deal) { setData((p) => ({ ...(p || {}), deal: r.deal })); setDisc(String(r.deal.discount_pct)); setPid(String(r.deal.product_id)); }
        setMsg(okMsg);
      } catch (e) { setMsg((e && e.message) || 'error'); }
      finally { setBusy(false); }
    }
    const fmtD = (ms) => ms ? new Date(ms).toLocaleString(ru ? 'ru-RU' : 'en-US') : '—';

    if (!data) {
      return <div style={{ padding: 20 }}><Sans s={12} c={C.dim}>{ru ? 'Загрузка…' : 'Loading…'}{msg ? (' — ' + msg) : ''}</Sans></div>;
    }
    const d = data.deal;
    const inp = { marginTop: 4, width: 120, background: 'transparent', border: `1px solid ${C.line}`, color: C.hi, padding: '8px 10px', fontFamily: F.sans, fontSize: 12, boxSizing: 'border-box' };

    return (
      <div>
        <SectionHead label={ru ? 'Сделка дня' : 'Daily deal'} />
        <div style={{ marginTop: 14, padding: '14px 16px', border: `1px solid ${C.line}`, background: C.panel2 }}>
          <Sans s={13} c={C.hi}>{ru ? d.name_ru : d.name_en} <span style={{ color: C.dim }}>({d.product_id})</span></Sans>
          <div style={{ marginTop: 6 }}>
            <Sans s={11} c={C.dim}>
              {ru ? 'Цена' : 'Price'}: {d.base_price}₽ → <span style={{ color: C.ember }}>{d.deal_price}₽</span> · {ru ? 'скидка' : 'discount'} {d.discount_pct}% · {ru ? 'до' : 'till'} {fmtD(d.ends_at)} · {d.overridden ? (ru ? 'ручная' : 'manual') : (ru ? 'авто π' : 'auto π')}
            </Sans>
          </div>
        </div>

        <div style={{ marginTop: 16, display: 'flex', gap: 14, alignItems: 'flex-end', flexWrap: 'wrap' }}>
          <div>
            <Sans s={10} c={C.dim} upper tracked>{ru ? 'Товар' : 'Product'}</Sans>
            <div>
              <select value={pid} onChange={(e) => setPid(e.target.value)}
                style={{ marginTop: 4, minWidth: 240, background: 'transparent', border: `1px solid ${C.line}`, color: C.hi, padding: '8px 10px', fontFamily: F.sans, fontSize: 12, cursor: 'pointer' }}>
                {(data.products || []).map((x) => (
                  <option key={x.id} value={x.id} style={{ background: C.bg }}>
                    {(ru ? x.name_ru : x.name_en)} · {x.price}₽ ({x.id})
                  </option>
                ))}
              </select>
            </div>
          </div>
          <div>
            <Sans s={10} c={C.dim} upper tracked>{ru ? 'Скидка, %' : 'Discount, %'}</Sans>
            <input value={disc} onChange={(e) => setDisc(e.target.value)} style={inp} />
          </div>
          <Btn kind="blood" disabled={busy}
            onClick={() => act(() => api('/api/owner/daily-deal', { method: 'PATCH', body: JSON.stringify({ product_id: pid, discount_pct: Number(disc) }) }), ru ? 'Применено' : 'Applied')}>
            {ru ? 'Применить' : 'Apply'}
          </Btn>
          <Btn kind="ghost" disabled={busy}
            onClick={() => act(() => api('/api/owner/daily-deal/reroll', { method: 'POST' }), ru ? 'Новый рандом' : 'Rerolled')}>
            🎲 {ru ? 'Новый рандом' : 'Reroll'}
          </Btn>
          <Btn kind="ghost" disabled={busy}
            onClick={() => act(() => api('/api/owner/daily-deal', { method: 'DELETE' }), ru ? 'Сброшено к авто' : 'Reset to auto')}>
            {ru ? 'Сброс (авто π)' : 'Reset (auto π)'}
          </Btn>
          {msg && <Sans s={11} c={C.ember}>{msg}</Sans>}
        </div>
        <div style={{ marginTop: 10 }}>
          <Sans s={9} c={C.dim}>{ru ? 'Время окончания всегда фиксировано (полночь) — рандом и скидка его не меняют.' : 'End time is always fixed (midnight) — reroll/discount never change it.'}</Sans>
        </div>
      </div>
    );
  }

  // ── Owner-only: вебмейл как отдельный верхнеуровневый view (не вкладка
  // админки). Шапка с тайтлом, тело — общий AdminMail.
  function MailScreen({ shop }) {
    const { profile, lang } = shop;
    const role = profile && profile.role;
    const ru = lang === 'ru';
    if (role !== 'owner') {
      return (
        <div style={{ padding: 80, textAlign: 'center' }}>
          <Display s={28} italic c={C.dim}>{ru ? 'Доступ только для владельца' : 'Owner only'}</Display>
        </div>
      );
    }
    return (
      <div style={{ overflow: 'auto', height: '100%' }}>
        <section style={{
          padding: '32px 48px 0', borderBottom: `1px solid ${C.line}`,
          background: 'linear-gradient(180deg, rgba(13,10,8,0.7) 0%, transparent 100%)',
        }}>
          <Mono c={C.ember}>· {ru ? 'ПОЧТА' : 'MAIL'}</Mono>
          <div style={{ marginTop: 8, marginBottom: 24 }}>
            <Display s={36} italic w={500}>support@furydayz.ru</Display>
          </div>
        </section>
        <section style={{ padding: '24px 48px 48px' }}>
          <AdminMail ru={ru} />
        </section>
      </div>
    );
  }

  // ── Админ-экран: три вкладки. «Админы» и «Бонус-коды» видны только owner. ─
  function AdminScreen({ shop }) {
    const { profile, lang } = shop;
    const role = profile && profile.role;
    const ru = lang === 'ru';

    const [tab, setTab] = React.useState('products');
    const tabs = [
      ['products', ru ? 'Товары'  : 'Products'],
      ['cats',     ru ? 'Разделы' : 'Categories'],
      ['users',    ru ? 'Пользователи' : 'Users'], // admin: только просмотр, owner: управление
      ['money',    ru ? 'Деньги' : 'Money'],
    ];
    if (role === 'owner') {
      tabs.push(['deal',    ru ? 'Сделка дня'     : 'Daily deal']);
      tabs.push(['admins',  ru ? 'Администраторы' : 'Admins']);
      tabs.push(['codes',   ru ? 'Бонус-коды'    : 'Bonus codes']);
      tabs.push(['content', ru ? 'Контент'        : 'Content']);
    }

    if (role !== 'admin' && role !== 'owner') {
      return (
        <div style={{ padding: 80, textAlign: 'center' }}>
          <Display s={28} italic c={C.dim}>{ru ? 'Доступ только для администраторов' : 'Admins only'}</Display>
        </div>
      );
    }

    return (
      <div style={{ overflow: 'auto', height: '100%' }}>
        <section style={{
          padding: '32px 48px 0', borderBottom: `1px solid ${C.line}`,
          background: 'linear-gradient(180deg, rgba(13,10,8,0.7) 0%, transparent 100%)',
        }}>
          <Mono c={C.ember}>· {ru ? 'АДМИНИСТРИРОВАНИЕ' : 'ADMINISTRATION'}</Mono>
          <div style={{ marginTop: 8, marginBottom: 18 }}>
            <Display s={36} italic w={500}>{ru ? 'Панель управления' : 'Control panel'}</Display>
          </div>
          <div style={{ display: 'flex', gap: 24 }}>
            {tabs.map(([k, label]) => (
              <button key={k} onClick={() => setTab(k)} style={{
                background: 'transparent', border: 'none', cursor: 'pointer',
                padding: '10px 0', position: 'relative',
                fontFamily: F.sans, fontSize: 11, letterSpacing: '0.2em',
                textTransform: 'uppercase', fontWeight: 500,
                color: tab === k ? C.hi : C.dim,
              }}>
                {label}
                {tab === k && <span style={{
                  position: 'absolute', left: 0, right: 0, bottom: -1,
                  height: 2, background: C.blood,
                }} />}
              </button>
            ))}
          </div>
        </section>

        <section style={{ padding: '32px 48px' }}>
          {tab === 'products' && <AdminProducts ru={ru} />}
          {tab === 'money'    && <AdminMoney ru={ru} />}
          {tab === 'cats'     && <AdminCategories ru={ru} />}
          {tab === 'users'    && (role === 'owner' || role === 'admin') && <AdminUsers ru={ru} canManage={role === 'owner'} />}
          {tab === 'deal'     && role === 'owner' && <AdminDailyDeal ru={ru} />}
          {tab === 'admins'   && role === 'owner' && <AdminAdmins ru={ru} ownerId={profile.steam_id} />}
          {tab === 'codes'    && role === 'owner' && <AdminBonusCodes ru={ru} />}
          {tab === 'content'  && role === 'owner' && <AdminContent ru={ru} />}
        </section>
      </div>
    );
  }

  // — Деньги: доход за день/месяц/год + % к прошлому периоду, топ предметов
  function AdminMoney({ ru }) {
    const [data, setData] = React.useState(null);
    const [err, setErr] = React.useState(null);
    const [per, setPer] = React.useState('month');
    React.useEffect(() => {
      api('/api/admin/stats').then(setData).catch((e) => setErr(e.message));
    }, []);
    const fmt = (n) => (Math.round(Number(n) || 0)).toLocaleString('ru-RU');
    if (err) return <div style={{ padding: 16 }}><Sans s={12} c={C.blood}>{err}</Sans></div>;
    if (!data) return <div style={{ padding: 16 }}><Sans s={12} c={C.dim}>{ru ? 'Загрузка…' : 'Loading…'}</Sans></div>;

    const cards = [
      ['day',   ru ? 'За сегодня' : 'Today',      ru ? 'вчера' : 'yesterday'],
      ['month', ru ? 'За месяц'   : 'This month', ru ? 'пр. месяц' : 'prev month'],
      ['year',  ru ? 'За год'     : 'This year',  ru ? 'пр. год' : 'prev year'],
    ];
    function Delta(props) {
      const pct = props.pct;
      if (pct === null || pct === undefined) return <Sans s={11} c={C.dim}>{ru ? 'нет данных за прошлый период' : 'no prior data'}</Sans>;
      const up = pct >= 0;
      return <Sans s={12} c={up ? '#6faf6f' : C.blood}>{(up ? '▲ +' : '▼ ') + pct + '%'}</Sans>;
    }

    const top = (data.top && data.top[per]) || [];

    return (
      <div>
        <div style={{ display: 'flex', gap: 16, flexWrap: 'wrap', marginBottom: 28 }}>
          {cards.map((c) => {
            const m = data.money[c[0]];
            return (
              <div key={c[0]} style={{ flex: '1 1 220px', minWidth: 200, padding: 20, background: C.panel2, border: `1px solid ${C.line}` }}>
                <Sans s={10} c={C.dim} upper tracked>{c[1]}</Sans>
                <div style={{ margin: '8px 0 6px' }}><Display s={28} c={C.hi}>{fmt(m.current)} ₽</Display></div>
                <Delta pct={m.pct} />
                <div style={{ marginTop: 4 }}><Sans s={10} c={C.dim}>{c[2]}: {fmt(m.prev)} ₽</Sans></div>
              </div>
            );
          })}
        </div>

        <div style={{ display: 'flex', alignItems: 'center', gap: 12, marginBottom: 12 }}>
          <Sans s={11} c={C.dim} upper tracked>{ru ? 'Топ предметов' : 'Top items'}</Sans>
          <div style={{ flex: 1 }} />
          {[['day', ru ? 'День' : 'Day'], ['month', ru ? 'Месяц' : 'Month'], ['year', ru ? 'Год' : 'Year']].map((o) => (
            <button key={o[0]} onClick={() => setPer(o[0])} style={{
              background: 'transparent', border: `1px solid ${per === o[0] ? C.ember : C.line}`,
              color: per === o[0] ? C.hi : C.dim, cursor: 'pointer', padding: '6px 14px',
              fontFamily: F.sans, fontSize: 10, letterSpacing: '0.12em', textTransform: 'uppercase',
            }}>{o[1]}</button>
          ))}
        </div>

        <table style={{ width: '100%', borderCollapse: 'collapse' }}>
          <thead>
            <tr style={{ textAlign: 'left', borderBottom: `1px solid ${C.line}` }}>
              <th style={{ padding: '8px', width: 36 }}><Sans s={9} c={C.dim} upper tracked>#</Sans></th>
              <th style={{ padding: '8px' }}><Sans s={9} c={C.dim} upper tracked>{ru ? 'Предмет' : 'Item'}</Sans></th>
              <th style={{ padding: '8px', textAlign: 'right' }}><Sans s={9} c={C.dim} upper tracked>{ru ? 'Продано' : 'Sold'}</Sans></th>
              <th style={{ padding: '8px', textAlign: 'right' }}><Sans s={9} c={C.dim} upper tracked>{ru ? 'Выручка' : 'Revenue'}</Sans></th>
            </tr>
          </thead>
          <tbody>
            {top.map((r, i) => (
              <tr key={r.product_id} style={{ borderBottom: `1px solid ${C.lineSoft}`, opacity: r.qty > 0 ? 1 : 0.4 }}>
                <td style={{ padding: '7px 8px' }}><Sans s={11} c={C.dim}>{i + 1}</Sans></td>
                <td style={{ padding: '7px 8px' }}><Sans s={12} c={C.hi}>{ru ? r.name_ru : r.name_en}</Sans> <Sans s={10} c={C.dim}>{r.product_id}</Sans></td>
                <td style={{ padding: '7px 8px', textAlign: 'right' }}><Sans s={12} c={r.qty > 0 ? C.hi : C.dim}>{r.qty}</Sans></td>
                <td style={{ padding: '7px 8px', textAlign: 'right' }}><Sans s={12} c={r.qty > 0 ? C.hi : C.dim}>{fmt(r.revenue)} ₽</Sans></td>
              </tr>
            ))}
            {top.length === 0 && (
              <tr><td colSpan={4} style={{ padding: 16 }}><Sans s={11} c={C.dim}>{ru ? 'Нет продаж за период' : 'No sales in period'}</Sans></td></tr>
            )}
          </tbody>
        </table>
        <div style={{ marginTop: 10 }}><Sans s={10} c={C.dim}>{ru ? '«Доход» — фактические пополнения баланса пользователями (реальные деньги). «Выручка» предмета — сумма списаний с баланса за покупки.' : 'Revenue = real balance top-ups by users. Item revenue = balance spent on purchases.'}</Sans></div>
      </div>
    );
  }

  // — Управление товарами
  function AdminProducts({ ru }) {
    const [items, setItems] = React.useState([]);
    const [editing, setEditing] = React.useState(null);  // объект продукта или 'new'
    const [busy, setBusy] = React.useState(false);
    const [err, setErr] = React.useState(null);
    const [catFilter, setCatFilter] = React.useState('all');
    const [sortBy, setSortBy] = React.useState('name_asc');  // name_asc|name_desc|price_asc|price_desc|id_asc|cat_asc

    async function reload() {
      try { const r = await api('/api/admin/products'); setItems(r.products || []); }
      catch (e) { setErr(e.message); }
    }
    React.useEffect(() => { reload(); }, []);

    const dynCats = window.FURY_CATEGORIES || [];

    // Применяем фильтр по категории и сортировку (всё на клиенте).
    const visible = React.useMemo(() => {
      let list = items.slice();
      if (catFilter !== 'all') list = list.filter((p) => p.cat === catFilter);
      const cmp = (a, b) => {
        switch (sortBy) {
          case 'name_asc':   return (a.name_ru || '').localeCompare(b.name_ru || '', 'ru');
          case 'name_desc':  return (b.name_ru || '').localeCompare(a.name_ru || '', 'ru');
          case 'price_asc':  return a.price - b.price;
          case 'price_desc': return b.price - a.price;
          case 'id_asc':     return a.id.localeCompare(b.id);
          case 'cat_asc':    return (a.cat || '').localeCompare(b.cat || '') || (a.name_ru || '').localeCompare(b.name_ru || '', 'ru');
          default:           return 0;
        }
      };
      return list.sort(cmp);
    }, [items, catFilter, sortBy]);

    async function save(p) {
      setBusy(true); setErr(null);
      try {
        if (editing === 'new') {
          await api('/api/admin/products', { method: 'POST', body: JSON.stringify(p) });
        } else {
          // _origId — это id до изменения; PATCH идёт по нему, в теле новый id передаётся отдельным полем.
          const url = '/api/admin/products/' + encodeURIComponent(p._origId || p.id);
          await api(url, { method: 'PATCH', body: JSON.stringify(p) });
        }
        setEditing(null);
        await reload();
        if (window.FURY_API && window.FURY_API.refreshProducts) window.FURY_API.refreshProducts();
      } catch (e) { setErr(e.data?.error || e.message); }
      finally { setBusy(false); }
    }

    async function remove(id) {
      if (!confirm(ru ? `Удалить «${id}»?` : `Delete "${id}"?`)) return;
      setBusy(true); setErr(null);
      try {
        await api('/api/admin/products/' + encodeURIComponent(id), { method: 'DELETE' });
        await reload();
        if (window.FURY_API && window.FURY_API.refreshProducts) window.FURY_API.refreshProducts();
      } catch (e) { setErr(e.data?.error || e.message); }
      finally { setBusy(false); }
    }

    if (editing) return <ProductEditor
      initial={editing === 'new' ? { id: '', cat: 'kits', name_ru: '', name_en: '', price: 100, gear: [], active: true } : editing}
      isNew={editing === 'new'}
      ru={ru}
      busy={busy}
      err={err}
      onCancel={() => setEditing(null)}
      onSave={save} />;

    const selectStyle = {
      background: 'transparent', border: `1px solid ${C.line}`, color: C.hi,
      padding: '6px 12px', fontFamily: F.sans, fontSize: 11,
      letterSpacing: '0.1em', cursor: 'pointer',
    };

    // Кликабельные заголовки колонок — тоггл сортировки.
    function sortBtn(col, label) {
      const asc = col + '_asc', desc = col + '_desc';
      const active = sortBy === asc || sortBy === desc;
      const next = sortBy === asc ? desc : asc;
      return (
        <button onClick={() => setSortBy(next)}
          style={{
            background: 'transparent', border: 'none', cursor: 'pointer', padding: 0,
            fontFamily: F.sans, fontSize: 9, letterSpacing: '0.12em', textTransform: 'uppercase',
            color: active ? C.hi : C.dim, fontWeight: active ? 700 : 400,
          }}>
          {label}{active ? (sortBy === asc ? ' ↑' : ' ↓') : ''}
        </button>
      );
    }

    return (
      <div>
        <div style={{ display: 'flex', alignItems: 'center', marginBottom: 18, gap: 12, flexWrap: 'wrap' }}>
          <Sans s={11} c={C.dim} upper tracked>{ru
            ? `Показано: ${visible.length} / ${items.length}`
            : `Visible: ${visible.length} / ${items.length}`}</Sans>

          {/* Фильтр по разделу */}
          <select value={catFilter} onChange={(e) => setCatFilter(e.target.value)} style={selectStyle}>
            <option value="all" style={{ background: C.bg }}>{ru ? 'Все разделы' : 'All categories'}</option>
            {dynCats.map((c) => (
              <option key={c.id} value={c.id} style={{ background: C.bg }}>
                {ru ? c.name_ru : c.name_en}
              </option>
            ))}
          </select>

          {/* Быстрый выбор сортировки */}
          <select value={sortBy} onChange={(e) => setSortBy(e.target.value)} style={selectStyle}>
            <option value="name_asc"   style={{ background: C.bg }}>{ru ? 'Название А→Я' : 'Name A→Z'}</option>
            <option value="name_desc"  style={{ background: C.bg }}>{ru ? 'Название Я→А' : 'Name Z→A'}</option>
            <option value="price_asc"  style={{ background: C.bg }}>{ru ? 'Цена ↑' : 'Price ↑'}</option>
            <option value="price_desc" style={{ background: C.bg }}>{ru ? 'Цена ↓' : 'Price ↓'}</option>
            <option value="id_asc"     style={{ background: C.bg }}>{ru ? 'ID А→Я' : 'ID A→Z'}</option>
            <option value="cat_asc"    style={{ background: C.bg }}>{ru ? 'По разделу' : 'By category'}</option>
          </select>

          <div style={{ flex: 1 }} />
          <Btn kind="blood" onClick={() => setEditing('new')}>{ru ? '+ Новый товар' : '+ New product'}</Btn>
        </div>
        {err && <div style={{ marginBottom: 12, padding: 10, background: 'rgba(150,30,30,0.18)' }}><Sans s={11} c={C.paper}>{err}</Sans></div>}
        <table style={{ width: '100%', borderCollapse: 'collapse' }}>
          <thead>
            <tr style={{ textAlign: 'left', borderBottom: `1px solid ${C.line}` }}>
              <th style={{ padding: '10px 8px' }}>{sortBtn('id',    'ID')}</th>
              <th style={{ padding: '10px 8px' }}>{sortBtn('cat',   ru ? 'Кат.' : 'Cat')}</th>
              <th style={{ padding: '10px 8px' }}>{sortBtn('name',  ru ? 'Название' : 'Name')}</th>
              <th style={{ padding: '10px 8px' }}>{sortBtn('price', ru ? 'Цена'    : 'Price')}</th>
              <th style={{ padding: '10px 8px' }}><Sans s={9} c={C.dim} upper tracked>—</Sans></th>
              <th style={{ padding: '10px 8px' }}><Sans s={9} c={C.dim} upper tracked>{ru ? 'Статус' : 'Status'}</Sans></th>
              <th style={{ padding: '10px 8px' }}></th>
            </tr>
          </thead>
          <tbody>
            {visible.map((p) => (
              <tr key={p.id} style={{ borderBottom: `1px solid ${C.lineSoft}` }}>
                <td style={{ padding: '10px 8px' }}><Mono c={C.hi}>{p.id}</Mono></td>
                <td style={{ padding: '10px 8px' }}><Sans s={11} c={C.text}>{p.cat}</Sans></td>
                <td style={{ padding: '10px 8px' }}><Sans s={12} c={C.hi}>{p.name_ru}</Sans></td>
                <td style={{ padding: '10px 8px' }}><Display s={14} c={C.ember}>{p.price} ₽</Display></td>
                <td style={{ padding: '10px 8px' }}><Sans s={10} c={C.dim}>{p.was ? p.was + ' ₽' : ''}</Sans></td>
                <td style={{ padding: '10px 8px' }}><Sans s={10} c={p.active ? '#3c8c3c' : C.dim}>{p.active ? (ru ? 'актив' : 'active') : (ru ? 'скрыт' : 'hidden')}</Sans></td>
                <td style={{ padding: '10px 8px', textAlign: 'right' }}>
                  <button onClick={() => setEditing(p)} style={miniBtn(C.line, C.hi)}>{ru ? 'править' : 'edit'}</button>
                  <button onClick={() => remove(p.id)} disabled={busy} style={miniBtn(C.blood, '#fff', 6)}>×</button>
                </td>
              </tr>
            ))}
          </tbody>
        </table>
      </div>
    );
  }

  function miniBtn(bd, fg, ml = 0) {
    return {
      background: 'transparent', border: `1px solid ${bd}`, color: fg, cursor: 'pointer',
      padding: '4px 10px', marginLeft: ml,
      fontFamily: F.sans, fontSize: 10, letterSpacing: '0.14em', textTransform: 'uppercase',
    };
  }

  // — Управление разделами (категориями)
  function AdminCategories({ ru }) {
    const [cats, setCats] = React.useState([]);
    const [editing, setEditing] = React.useState(null);  // объект категории или 'new'
    const [busy, setBusy] = React.useState(false);
    const [err, setErr] = React.useState(null);

    async function reload() {
      try {
        const r = await api('/api/categories');
        setCats(r.categories || []);
      } catch (e) { setErr(e.message); }
    }
    React.useEffect(() => { reload(); }, []);

    async function save(c) {
      setBusy(true); setErr(null);
      try {
        if (editing === 'new') {
          await api('/api/admin/categories', { method: 'POST', body: JSON.stringify(c) });
        } else {
          const url = '/api/admin/categories/' + encodeURIComponent(c._origId || c.id);
          await api(url, { method: 'PATCH', body: JSON.stringify(c) });
        }
        setEditing(null);
        await reload();
        if (window.FURY_API && window.FURY_API.refreshCategories) window.FURY_API.refreshCategories();
        if (window.FURY_API && window.FURY_API.refreshProducts)   window.FURY_API.refreshProducts();
      } catch (e) { setErr(e.data?.error || e.message); }
      finally { setBusy(false); }
    }

    async function remove(id) {
      if (!confirm(ru ? `Удалить раздел «${id}»?` : `Delete category "${id}"?`)) return;
      setBusy(true); setErr(null);
      try {
        await api('/api/admin/categories/' + encodeURIComponent(id), { method: 'DELETE' });
        await reload();
        if (window.FURY_API && window.FURY_API.refreshCategories) window.FURY_API.refreshCategories();
      } catch (e) {
        if (e.data?.error === 'in_use') {
          setErr(ru
            ? `Раздел используется в ${e.data.products} товаре(ах). Сначала переместите их в другой раздел.`
            : `Category is used by ${e.data.products} product(s). Move them to another category first.`);
        } else setErr(e.data?.error || e.message);
      }
      finally { setBusy(false); }
    }

    if (editing) return <CategoryEditor
      initial={editing === 'new' ? { id: '', name_ru: '', name_en: '', sort_order: cats.length * 10 } : { ...editing, _origId: editing.id }}
      isNew={editing === 'new'}
      ru={ru}
      busy={busy}
      err={err}
      onCancel={() => setEditing(null)}
      onSave={save} />;

    return (
      <div style={{ maxWidth: 760 }}>
        <div style={{ display: 'flex', alignItems: 'center', marginBottom: 18 }}>
          <Sans s={11} c={C.dim} upper tracked>{ru ? `Разделов: ${cats.length}` : `Total: ${cats.length}`}</Sans>
          <div style={{ flex: 1 }} />
          <Btn kind="blood" onClick={() => setEditing('new')}>{ru ? '+ Новый раздел' : '+ New category'}</Btn>
        </div>
        {err && <div style={{ marginBottom: 12, padding: 10, background: 'rgba(150,30,30,0.18)' }}><Sans s={11} c={C.paper}>{err}</Sans></div>}
        <table style={{ width: '100%', borderCollapse: 'collapse' }}>
          <thead>
            <tr style={{ textAlign: 'left', borderBottom: `1px solid ${C.line}` }}>
              {['ID', ru ? 'Название (ru)' : 'Name (ru)', ru ? 'Название (en)' : 'Name (en)', ru ? 'Сорт' : 'Sort', ''].map((h, i) => (
                <th key={i} style={{ padding: '10px 8px' }}><Sans s={9} c={C.dim} upper tracked>{h}</Sans></th>
              ))}
            </tr>
          </thead>
          <tbody>
            {cats.map((c) => (
              <tr key={c.id} style={{ borderBottom: `1px solid ${C.lineSoft}` }}>
                <td style={{ padding: '10px 8px' }}><Mono c={C.hi}>{c.id}</Mono></td>
                <td style={{ padding: '10px 8px' }}><Sans s={12} c={C.text}>{c.name_ru}</Sans></td>
                <td style={{ padding: '10px 8px' }}><Sans s={12} c={C.text}>{c.name_en}</Sans></td>
                <td style={{ padding: '10px 8px' }}><Sans s={10} c={C.dim}>{c.sort_order}</Sans></td>
                <td style={{ padding: '10px 8px', textAlign: 'right' }}>
                  <button onClick={() => setEditing(c)} style={miniBtn(C.line, C.hi)}>{ru ? 'править' : 'edit'}</button>
                  <button onClick={() => remove(c.id)} disabled={busy} style={miniBtn(C.blood, '#fff', 6)}>×</button>
                </td>
              </tr>
            ))}
          </tbody>
        </table>
      </div>
    );
  }

  function CategoryEditor({ initial, isNew, ru, busy, err, onCancel, onSave }) {
    const [c, setC] = React.useState({ ...initial });
    const set = (k) => (e) => setC({ ...c, [k]: e.target.value });
    return (
      <div style={{ maxWidth: 560 }}>
        <Display s={22} italic w={500}>
          {isNew ? (ru ? 'Новый раздел' : 'New category') : (ru ? 'Редактирование' : 'Editing') + ' · ' + (c._origId || c.id)}
        </Display>
        <div style={{ marginTop: 18, display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 14 }}>
          <Field label="ID" value={c.id} onChange={set('id')} placeholder="kits / weapons / pvp / ..." />
          <Field label={ru ? 'Сортировка' : 'Sort order'} type="number" value={c.sort_order ?? 0} onChange={set('sort_order')} />
          <Field label={ru ? 'Название (ru)' : 'Name (ru)'} value={c.name_ru} onChange={set('name_ru')} />
          <Field label={ru ? 'Название (en)' : 'Name (en)'} value={c.name_en} onChange={set('name_en')} />
        </div>
        {err && <div style={{ marginTop: 12, padding: 10, background: 'rgba(150,30,30,0.18)' }}><Sans s={11} c={C.paper}>{err}</Sans></div>}
        <div style={{ marginTop: 22, display: 'flex', gap: 10 }}>
          <Btn kind="ghost" onClick={onCancel} disabled={busy}>{ru ? 'Отмена' : 'Cancel'}</Btn>
          <div style={{ flex: 1 }} />
          <Btn kind="blood" onClick={() => onSave(c)} disabled={busy}>
            {busy ? '…' : (isNew ? (ru ? 'Создать' : 'Create') : (ru ? 'Сохранить' : 'Save'))}
          </Btn>
        </div>
      </div>
    );
  }

  function ProductEditor({ initial, isNew, ru, busy, err, onCancel, onSave }) {
    const [p, setP] = React.useState({ ...initial, _origId: initial.id });
    const [rawForCrop, setRawForCrop] = React.useState(null);  // data-URL до кропа
    const set = (k) => (e) => setP({ ...p, [k]: e.target.value });
    const setGear = (e) => setP({ ...p, gear: e.target.value.split(',').map((s) => s.trim()).filter(Boolean) });
    const cats = (window.FURY_CATEGORIES || []);
    return (
      <div style={{ maxWidth: 600 }}>
        <Display s={22} italic w={500}>{isNew ? (ru ? 'Новый товар' : 'New product') : (ru ? 'Редактирование' : 'Editing') + ' · ' + (p._origId || p.id)}</Display>
        <div style={{ marginTop: 18, display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 14 }}>
          <Field label="ID" value={p.id} onChange={set('id')} placeholder="kit-bandit" />
          <div>
            <Sans s={9} c={C.dim} upper tracked>{ru ? 'Категория' : 'Category'}</Sans>
            <select value={p.cat} onChange={set('cat')}
              style={{
                marginTop: 4, width: '100%', background: 'transparent',
                border: `1px solid ${C.line}`, color: C.hi, padding: '8px 12px',
                fontFamily: F.sans, fontSize: 13,
              }}>
              {cats.length === 0 && <option value={p.cat} style={{ background: C.bg }}>{p.cat}</option>}
              {cats.map((c) => (
                <option key={c.id} value={c.id} style={{ background: C.bg }}>
                  {ru ? c.name_ru : c.name_en} ({c.id})
                </option>
              ))}
            </select>
          </div>
          <Field label={ru ? 'Название (ru)' : 'Name (ru)'} value={p.name_ru} onChange={set('name_ru')} />
          <Field label={ru ? 'Название (en)' : 'Name (en)'} value={p.name_en} onChange={set('name_en')} />
          <Field label={ru ? 'Цена' : 'Price'} type="number" value={p.price} onChange={set('price')} />
          <Field label={ru ? 'Было (опц.)' : 'Was (opt.)'} type="number" value={p.was ?? ''} onChange={set('was')} />
        </div>
        <div style={{ marginTop: 14 }}>
          <Field label={ru ? 'Состав (через запятую)' : 'Gear (comma-separated)'} value={(p.gear || []).join(', ')} onChange={setGear} placeholder="SKS, MP5, каска, рюкзак" />
        </div>

        {/* Описание товара — отдельные ru/en. Опционально; если пусто, фронт берёт fallback из data.js (хардкод). */}
        <div style={{ marginTop: 14, display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 14 }}>
          <div>
            <Sans s={9} c={C.dim} upper tracked>{ru ? 'Описание (ru)' : 'Description (ru)'}</Sans>
            <textarea value={p.description_ru || ''} onChange={set('description_ru')} rows={4}
              placeholder={ru ? 'Что это, как работает, для кого…' : 'What it is, how it works…'}
              maxLength={2000}
              style={{ marginTop: 4, width: '100%', boxSizing: 'border-box', background: 'transparent', border: `1px solid ${C.line}`, color: C.hi, padding: '8px 10px', fontFamily: F.sans, fontSize: 12, lineHeight: 1.5, resize: 'vertical' }} />
          </div>
          <div>
            <Sans s={9} c={C.dim} upper tracked>{ru ? 'Описание (en)' : 'Description (en)'}</Sans>
            <textarea value={p.description_en || ''} onChange={set('description_en')} rows={4}
              placeholder={ru ? 'Английская версия (опц.)' : 'English version (opt.)'}
              maxLength={2000}
              style={{ marginTop: 4, width: '100%', boxSizing: 'border-box', background: 'transparent', border: `1px solid ${C.line}`, color: C.hi, padding: '8px 10px', fontFamily: F.sans, fontSize: 12, lineHeight: 1.5, resize: 'vertical' }} />
          </div>
        </div>

        {/* Картинка товара. Загрузка через FileReader → base64 → отправляется в image_data. */}
        <div style={{ marginTop: 18 }}>
          <Sans s={9} c={C.dim} upper tracked>{ru ? 'Картинка слота (PNG/JPG/WebP/GIF, до 4 МБ)' : 'Slot image (PNG/JPG/WebP/GIF, up to 4 MB)'}</Sans>
          <div style={{ marginTop: 8, display: 'flex', gap: 12, alignItems: 'flex-start' }}>
            <div style={{ width: 140, height: 90, background: C.panel2, border: `1px solid ${C.line}`, overflow: 'hidden', flexShrink: 0 }}>
              {(p.image_data || p.image_url) ? (
                <img src={p.image_data || p.image_url} alt="" style={{ width: '100%', height: '100%', objectFit: 'cover' }} />
              ) : (
                <div style={{ width: '100%', height: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
                  <Sans s={10} c={C.dim}>{ru ? 'нет картинки' : 'no image'}</Sans>
                </div>
              )}
            </div>
            <div style={{ flex: 1 }}>
              <input type="file" accept="image/png,image/jpeg,image/webp,image/gif"
                onChange={(e) => {
                  const file = e.target.files && e.target.files[0];
                  if (!file) return;
                  if (file.size > 4 * 1024 * 1024) { alert(ru ? 'Файл больше 4 МБ' : 'File larger than 4 MB'); return; }
                  const reader = new FileReader();
                  // Сразу открываем модал-кроппер вместо моментального сохранения.
                  reader.onload = () => setRawForCrop(reader.result);
                  reader.readAsDataURL(file);
                  // сбросим value, чтобы повторный выбор того же файла снова сработал
                  e.target.value = '';
                }} />
              {(p.image_data || p.image_url) && (
                <>
                  <button onClick={() => setRawForCrop(p.image_data || p.image_url)}
                    style={{
                      marginTop: 8, marginRight: 8,
                      background: 'transparent', border: `1px solid ${C.line}`, color: C.hi,
                      cursor: 'pointer', padding: '4px 10px', fontFamily: F.sans, fontSize: 10,
                      letterSpacing: '0.14em', textTransform: 'uppercase',
                    }}>{ru ? '⤢ перекадрировать' : '⤢ recrop'}</button>
                  <button onClick={() => setP({ ...p, image_data: undefined, image_url: null })}
                    style={{
                      marginTop: 8, background: 'transparent', border: `1px solid ${C.blood}`, color: '#fff',
                      cursor: 'pointer', padding: '4px 10px', fontFamily: F.sans, fontSize: 10,
                      letterSpacing: '0.14em', textTransform: 'uppercase',
                    }}>{ru ? '× убрать картинку' : '× remove image'}</button>
                </>
              )}
            </div>
          </div>
        </div>

        <div style={{ marginTop: 14, display: 'flex', alignItems: 'center', gap: 10 }}>
          <label style={{ display: 'flex', alignItems: 'center', gap: 8, cursor: 'pointer' }}>
            <input type="checkbox" checked={!!p.active} onChange={(e) => setP({ ...p, active: e.target.checked })} />
            <Sans s={11} c={C.hi}>{ru ? 'Виден в магазине' : 'Visible in shop'}</Sans>
          </label>
        </div>
        {err && <div style={{ marginTop: 12, padding: 10, background: 'rgba(150,30,30,0.18)' }}><Sans s={11} c={C.paper}>{err}</Sans></div>}
        <div style={{ marginTop: 22, display: 'flex', gap: 10 }}>
          <Btn kind="ghost" onClick={onCancel} disabled={busy}>{ru ? 'Отмена' : 'Cancel'}</Btn>
          <div style={{ flex: 1 }} />
          <Btn kind="blood" onClick={() => onSave(p)} disabled={busy}>{busy ? '…' : (isNew ? (ru ? 'Создать' : 'Create') : (ru ? 'Сохранить' : 'Save'))}</Btn>
        </div>
        {rawForCrop && (
          <ImageCropper
            src={rawForCrop}
            ru={ru}
            onCancel={() => setRawForCrop(null)}
            onCrop={(dataUrl) => { setP({ ...p, image_data: dataUrl }); setRawForCrop(null); }}
          />
        )}
      </div>
    );
  }

  // Crop-инструмент на чистом canvas. Свободная форма: прямоугольник можно
  // двигать в любую точку картинки, тянуть за 4 угла И 4 рёбра, пропорции
  // не зафиксированы.
  function ImageCropper({ src, ru, onCrop, onCancel }) {
    const containerRef = React.useRef(null);
    const imgRef = React.useRef(null);
    const [layout, setLayout] = React.useState(null);  // { dw, dh, ox, oy }
    const [crop, setCrop]     = React.useState(null);  // { x, y, w, h } в координатах контейнера
    const [drag, setDrag]     = React.useState(null);

    function recompute() {
      const container = containerRef.current;
      const img = imgRef.current;
      if (!container || !img || !img.naturalWidth) return;
      const cw = container.clientWidth, ch = container.clientHeight;
      const nw = img.naturalWidth, nh = img.naturalHeight;
      const scale = Math.min(cw / nw, ch / nh);
      const dw = nw * scale, dh = nh * scale;
      const ox = (cw - dw) / 2, oy = (ch - dh) / 2;
      setLayout({ dw, dh, ox, oy });
      // Стартовый crop — ~80% от размера картинки, центрирован.
      const w = dw * 0.8, h = dh * 0.8;
      setCrop({ x: ox + (dw - w) / 2, y: oy + (dh - h) / 2, w, h });
    }

    function onPointerDown(e, type) {
      e.preventDefault(); e.stopPropagation();
      const r = containerRef.current.getBoundingClientRect();
      setDrag({
        type,
        startX: e.clientX - r.left,
        startY: e.clientY - r.top,
        orig: { ...crop },
      });
    }
    function onPointerMove(e) {
      if (!drag || !layout) return;
      const r = containerRef.current.getBoundingClientRect();
      const x = e.clientX - r.left, y = e.clientY - r.top;
      const dx = x - drag.startX, dy = y - drag.startY;
      const { ox, oy, dw, dh } = layout;
      const minSize = 16;
      const right  = ox + dw;
      const bottom = oy + dh;
      let { x: ox0, y: oy0, w, h } = drag.orig;

      if (drag.type === 'move') {
        const nx = clamp(ox0 + dx, ox, right  - w);
        const ny = clamp(oy0 + dy, oy, bottom - h);
        setCrop({ x: nx, y: ny, w, h });
        return;
      }

      // Каждая сторона/угол двигает только относящиеся к ней рёбра.
      // dx/dy без блокировки aspect — пользователь сам решает форму.
      const t = drag.type;       // 'move' | 'tl'|'tr'|'bl'|'br' | 't'|'r'|'b'|'l'
      // Стартовые края:
      let l = ox0, top = oy0, rgt = ox0 + w, bot = oy0 + h;
      if (t.includes('l')) l   = clamp(ox0 + dx, ox, rgt - minSize);
      if (t.includes('r')) rgt = clamp(ox0 + w + dx, l + minSize, right);
      if (t.includes('t')) top = clamp(oy0 + dy, oy, bot - minSize);
      if (t.includes('b')) bot = clamp(oy0 + h + dy, top + minSize, bottom);
      setCrop({ x: l, y: top, w: rgt - l, h: bot - top });
    }
    function onPointerUp() { setDrag(null); }

    React.useEffect(() => {
      const m = (e) => onPointerMove(e);
      const u = () => onPointerUp();
      if (drag) {
        window.addEventListener('mousemove', m);
        window.addEventListener('mouseup', u);
        return () => { window.removeEventListener('mousemove', m); window.removeEventListener('mouseup', u); };
      }
    }, [drag, layout, crop]);

    function save() {
      if (!crop || !layout) return;
      const img = imgRef.current;
      const k = img.naturalWidth / layout.dw;  // натур-пикселей на отображаемый пиксель
      const sx = (crop.x - layout.ox) * k;
      const sy = (crop.y - layout.oy) * k;
      const sw = crop.w * k;
      const sh = crop.h * k;
      // Ограничиваем длинную сторону 1200 px — достаточно для retina, файл лёгкий.
      const maxSide = 1200;
      const longest = Math.max(sw, sh);
      const scale = longest > maxSide ? maxSide / longest : 1;
      const outW = Math.max(1, Math.round(sw * scale));
      const outH = Math.max(1, Math.round(sh * scale));
      const canvas = document.createElement('canvas');
      canvas.width = outW;
      canvas.height = outH;
      const ctx = canvas.getContext('2d');
      ctx.imageSmoothingEnabled = true;
      ctx.imageSmoothingQuality = 'high';
      ctx.drawImage(img, sx, sy, sw, sh, 0, 0, outW, outH);

      // Определяем, есть ли реально прозрачные пиксели. Если да — PNG (сохранит
      // прозрачность; на тёмной FURY-карточке фон будет просвечивать естественно).
      // Если нет — JPEG q=0.9 (в ~3-5 раз легче).
      let hasAlpha = false;
      try {
        const data = ctx.getImageData(0, 0, outW, outH).data;
        // Сэмплируем по сетке ~64 точки, чтобы не сканировать миллионы пикселей.
        const stepX = Math.max(1, Math.floor(outW / 8));
        const stepY = Math.max(1, Math.floor(outH / 8));
        outer: for (let y = 0; y < outH; y += stepY) {
          for (let x = 0; x < outW; x += stepX) {
            const i = (y * outW + x) * 4 + 3;
            if (data[i] < 255) { hasAlpha = true; break outer; }
          }
        }
      } catch (_) { /* если CORS-исходник — getImageData упадёт, оставим JPEG */ }

      const dataUrl = hasAlpha
        ? canvas.toDataURL('image/png')
        : canvas.toDataURL('image/jpeg', 0.9);
      onCrop(dataUrl);
    }

    return (
      <div style={{
        position: 'fixed', inset: 0, zIndex: 1200, background: 'rgba(13,10,8,0.86)',
        display: 'flex', alignItems: 'center', justifyContent: 'center',
      }}>
        <div style={{
          background: C.bg, border: `1px solid ${C.line}`, width: 'min(720px, 92vw)',
          padding: 18, display: 'flex', flexDirection: 'column', gap: 14,
        }}>
          <div style={{ display: 'flex', alignItems: 'center' }}>
            <Display s={20} italic w={500}>{ru ? 'Кадрирование' : 'Crop image'}</Display>
            <div style={{ flex: 1 }} />
            <button onClick={onCancel} style={{
              background: 'transparent', border: 'none', cursor: 'pointer', color: C.dim,
              fontSize: 20, padding: '0 6px',
            }}>×</button>
          </div>
          <div ref={containerRef}
            style={{
              position: 'relative', width: '100%', height: 380,
              background: '#000', userSelect: 'none', overflow: 'hidden',
              cursor: drag ? (drag.type === 'move' ? 'grabbing' : 'crosshair') : 'default',
            }}
            onMouseDown={(e) => {
              // Клик не по картинке — не реагируем (зона серая).
              if (e.target !== imgRef.current && e.target !== containerRef.current) return;
            }}>
            <img ref={imgRef} src={src} alt=""
              draggable={false}
              onLoad={recompute}
              style={{
                position: 'absolute',
                left: layout ? layout.ox : 0,
                top:  layout ? layout.oy : 0,
                width:  layout ? layout.dw : 'auto',
                height: layout ? layout.dh : 'auto',
                pointerEvents: 'none',
              }} />
            {/* затемнение вокруг crop */}
            {crop && (
              <div style={{
                position: 'absolute', inset: 0, pointerEvents: 'none',
                boxShadow: `0 0 0 9999px rgba(0,0,0,0.55)`,
                clipPath: `polygon(0 0, 100% 0, 100% 100%, 0 100%, 0 ${crop.y}px, ${crop.x}px ${crop.y}px, ${crop.x}px ${crop.y + crop.h}px, ${crop.x + crop.w}px ${crop.y + crop.h}px, ${crop.x + crop.w}px ${crop.y}px, 0 ${crop.y}px)`,
              }} />
            )}
            {/* сам прямоугольник + ручки (4 угла + 4 ребра) */}
            {crop && (
              <div
                onMouseDown={(e) => onPointerDown(e, 'move')}
                style={{
                  position: 'absolute',
                  left: crop.x, top: crop.y, width: crop.w, height: crop.h,
                  border: '1.5px solid rgba(232,220,203,0.95)',
                  boxSizing: 'border-box', cursor: 'grab',
                  boxShadow: '0 0 0 1px rgba(0,0,0,0.5)',
                }}>
                {/* Углы */}
                {[
                  ['tl', { left: -7, top: -7,    cursor: 'nwse-resize' }],
                  ['tr', { right: -7, top: -7,   cursor: 'nesw-resize' }],
                  ['bl', { left: -7, bottom: -7, cursor: 'nesw-resize' }],
                  ['br', { right: -7, bottom: -7, cursor: 'nwse-resize' }],
                ].map(([k, pos]) => (
                  <div key={k}
                    onMouseDown={(e) => onPointerDown(e, k)}
                    style={{
                      position: 'absolute', width: 14, height: 14, zIndex: 2,
                      background: '#e8dccb', border: '1px solid rgba(0,0,0,0.6)',
                      ...pos,
                    }} />
                ))}
                {/* Рёбра: тонкие хит-зоны на каждой стороне (показываем как точку посередине) */}
                {[
                  ['t', { left: '50%', top: -5, transform: 'translateX(-50%)', cursor: 'ns-resize', width: 22, height: 10 }],
                  ['b', { left: '50%', bottom: -5, transform: 'translateX(-50%)', cursor: 'ns-resize', width: 22, height: 10 }],
                  ['l', { top: '50%', left: -5, transform: 'translateY(-50%)', cursor: 'ew-resize', width: 10, height: 22 }],
                  ['r', { top: '50%', right: -5, transform: 'translateY(-50%)', cursor: 'ew-resize', width: 10, height: 22 }],
                ].map(([k, pos]) => (
                  <div key={k}
                    onMouseDown={(e) => onPointerDown(e, k)}
                    style={{
                      position: 'absolute', zIndex: 2,
                      background: 'rgba(232,220,203,0.85)', border: '1px solid rgba(0,0,0,0.6)',
                      ...pos,
                    }} />
                ))}
                {/* визуальная сетка третей внутри (помогает компоновке) */}
                <div style={{ position: 'absolute', inset: 0, pointerEvents: 'none' }}>
                  <div style={{ position: 'absolute', left: '33.33%', top: 0, bottom: 0, width: 1, background: 'rgba(255,255,255,0.18)' }} />
                  <div style={{ position: 'absolute', left: '66.66%', top: 0, bottom: 0, width: 1, background: 'rgba(255,255,255,0.18)' }} />
                  <div style={{ position: 'absolute', top: '33.33%', left: 0, right: 0, height: 1, background: 'rgba(255,255,255,0.18)' }} />
                  <div style={{ position: 'absolute', top: '66.66%', left: 0, right: 0, height: 1, background: 'rgba(255,255,255,0.18)' }} />
                </div>
                {/* индикатор размеров в углу */}
                <div style={{
                  position: 'absolute', left: 4, bottom: 4,
                  background: 'rgba(0,0,0,0.7)', padding: '2px 6px',
                  color: '#e8dccb', fontFamily: F.mono, fontSize: 10,
                  pointerEvents: 'none',
                }}>
                  {Math.round(crop.w)} × {Math.round(crop.h)}
                </div>
              </div>
            )}
          </div>
          <Sans s={10} c={C.dim}>
            {ru
              ? 'Тяните за центр — двигать всю область. За углы — менять оба размера, за рёбра — только одно. Соотношение сторон свободное.'
              : 'Drag the centre to move. Pull a corner to resize freely, pull an edge to resize one side. No aspect lock.'}
          </Sans>
          <div style={{ display: 'flex', gap: 10 }}>
            <Btn kind="ghost" onClick={onCancel}>{ru ? 'Отмена' : 'Cancel'}</Btn>
            <div style={{ flex: 1 }} />
            <Btn kind="blood" onClick={save}>{ru ? 'Сохранить кадр' : 'Apply crop'}</Btn>
          </div>
        </div>
      </div>
    );
  }
  function clamp(v, a, b) { return Math.max(a, Math.min(b, v)); }

  function Field({ label, value, onChange, placeholder, type = 'text', disabled }) {
    return (
      <div>
        <Sans s={9} c={C.dim} upper tracked>{label}</Sans>
        <input type={type} value={value ?? ''} onChange={onChange} placeholder={placeholder} disabled={disabled}
          style={{
            marginTop: 4, width: '100%', background: 'transparent',
            border: `1px solid ${disabled ? C.lineSoft : C.line}`, color: disabled ? C.dim : C.hi,
            padding: '8px 12px', fontFamily: F.sans, fontSize: 13,
          }} />
      </div>
    );
  }

  // — Управление администраторами
  function AdminAdmins({ ru, ownerId }) {
    const [admins, setAdmins] = React.useState([]);
    const [owners, setOwners] = React.useState([]);
    const [sid, setSid] = React.useState('');
    const [note, setNote] = React.useState('');
    const [busy, setBusy] = React.useState(false);
    const [err, setErr] = React.useState(null);

    async function reload() {
      try {
        const r = await api('/api/owner/admins');
        setAdmins(r.admins || []);
        // r.owners — массив; на старых билдах был r.owner (одиночка) — поддержим оба.
        setOwners(Array.isArray(r.owners) ? r.owners : (r.owner ? [r.owner] : []));
      } catch (e) { setErr(e.message); }
    }
    React.useEffect(() => { reload(); }, []);

    async function add() {
      setBusy(true); setErr(null);
      try {
        await api('/api/owner/admins', { method: 'POST', body: JSON.stringify({ steam_id: sid.trim(), note: note.trim() }) });
        setSid(''); setNote('');
        await reload();
      } catch (e) { setErr(e.data?.error || e.message); }
      finally { setBusy(false); }
    }

    async function remove(s) {
      if (!confirm(ru ? `Удалить админа ${s}?` : `Remove admin ${s}?`)) return;
      setBusy(true); setErr(null);
      try { await api('/api/owner/admins/' + s, { method: 'DELETE' }); await reload(); }
      catch (e) { setErr(e.data?.error || e.message); }
      finally { setBusy(false); }
    }

    return (
      <div style={{ maxWidth: 760 }}>
        <div style={{ padding: 18, background: C.panel2, border: `1px solid ${C.line}`, marginBottom: 22 }}>
          <Mono c={C.ember}>· {ru ? `ВЛАДЕЛЬЦЫ (${owners.length})` : `OWNERS (${owners.length})`}</Mono>
          <div style={{ marginTop: 6 }}>
            {owners.length === 0 && <Mono c={C.dim}>—</Mono>}
            {owners.map((o) => (
              <div key={o} style={{ display: 'flex', alignItems: 'center', padding: '4px 0' }}>
                <Mono c={C.hi}>{o}</Mono>
              </div>
            ))}
          </div>
          <div style={{ marginTop: 10 }}>
            <Sans s={10} c={C.dim}>{ru
              ? 'Из переменной OWNER_STEAM_ID в .env (через запятую). Чтобы добавить/убрать владельца — отредактируйте .env на сервере и перезапустите fury.service.'
              : 'Configured via OWNER_STEAM_ID in .env (comma-separated). To add/remove an owner — edit .env on the server and restart fury.service.'}</Sans>
          </div>
        </div>

        <Mono c={C.ember}>· {ru ? 'ДОБАВИТЬ АДМИНА' : 'ADD ADMIN'}</Mono>
        <div style={{ marginTop: 10, display: 'flex', gap: 10 }}>
          <input value={sid} onChange={(e) => setSid(e.target.value)} placeholder="SteamID64 (76561198…)"
            style={{ flex: 2, background: 'transparent', border: `1px solid ${C.line}`, color: C.hi, padding: '10px 14px', fontFamily: F.mono, fontSize: 13 }} />
          <input value={note} onChange={(e) => setNote(e.target.value)} placeholder={ru ? 'заметка (опц.)' : 'note (opt.)'}
            style={{ flex: 1, background: 'transparent', border: `1px solid ${C.line}`, color: C.hi, padding: '10px 14px', fontFamily: F.sans, fontSize: 13 }} />
          <Btn kind="blood" onClick={add} disabled={busy || !sid.trim()}>{ru ? '+ Добавить' : '+ Add'}</Btn>
        </div>
        {err && <div style={{ marginTop: 10, padding: 8, background: 'rgba(150,30,30,0.18)' }}><Sans s={11} c={C.paper}>{err}</Sans></div>}

        <div style={{ marginTop: 28 }}>
          <Mono c={C.ember}>· {ru ? `АДМИНИСТРАТОРЫ (${admins.length})` : `ADMINS (${admins.length})`}</Mono>
          <table style={{ width: '100%', borderCollapse: 'collapse', marginTop: 10 }}>
            <thead>
              <tr style={{ textAlign: 'left', borderBottom: `1px solid ${C.line}` }}>
                {['Steam ID', ru ? 'Добавил' : 'Added by', ru ? 'Когда' : 'When', ru ? 'Заметка' : 'Note', ''].map((h, i) => (
                  <th key={i} style={{ padding: '10px 8px' }}><Sans s={9} c={C.dim} upper tracked>{h}</Sans></th>
                ))}
              </tr>
            </thead>
            <tbody>
              {admins.map((a) => (
                <tr key={a.steam_id} style={{ borderBottom: `1px solid ${C.lineSoft}` }}>
                  <td style={{ padding: '10px 8px' }}><Mono c={C.hi}>{a.steam_id}</Mono></td>
                  <td style={{ padding: '10px 8px' }}><Mono c={C.dim}>{a.added_by.slice(-4)}</Mono></td>
                  <td style={{ padding: '10px 8px' }}><Sans s={11} c={C.text}>{new Date(a.added_at).toLocaleDateString()}</Sans></td>
                  <td style={{ padding: '10px 8px' }}><Sans s={11} c={C.text}>{a.note || ''}</Sans></td>
                  <td style={{ padding: '10px 8px', textAlign: 'right' }}>
                    <button onClick={() => remove(a.steam_id)} disabled={busy} style={miniBtn(C.blood, '#fff')}>{ru ? 'удалить' : 'remove'}</button>
                  </td>
                </tr>
              ))}
              {admins.length === 0 && (
                <tr><td colSpan={5} style={{ padding: '20px 8px' }}><Sans s={11} c={C.dim}>{ru ? 'Админы пока не добавлены.' : 'No admins yet.'}</Sans></td></tr>
              )}
            </tbody>
          </table>
        </div>
      </div>
    );
  }

  // — Управление бонус-кодами
  function AdminBonusCodes({ ru }) {
    const [codes, setCodes] = React.useState([]);
    const [amount, setAmount] = React.useState(500);
    const [ttl, setTtl] = React.useState(24);
    const [maxUses, setMaxUses] = React.useState(1);
    const [lastCode, setLastCode] = React.useState(null);
    const [busy, setBusy] = React.useState(false);
    const [err, setErr] = React.useState(null);

    async function reload() {
      try { const r = await api('/api/owner/bonus-codes'); setCodes(r.codes || []); }
      catch (e) { setErr(e.message); }
    }
    React.useEffect(() => { reload(); }, []);

    async function gen() {
      setBusy(true); setErr(null);
      try {
        const r = await api('/api/owner/bonus-codes', {
          method: 'POST',
          body: JSON.stringify({ amount: Number(amount), ttl_hours: Number(ttl), max_uses: Number(maxUses) }),
        });
        setLastCode(r);
        await reload();
      } catch (e) { setErr(e.data?.error || e.message); }
      finally { setBusy(false); }
    }

    async function revoke(code) {
      if (!confirm(ru ? `Отозвать код ${code}?` : `Revoke code ${code}?`)) return;
      setBusy(true);
      try { await api('/api/owner/bonus-codes/' + code, { method: 'DELETE' }); await reload(); }
      catch (e) { setErr(e.data?.error || e.message); }
      finally { setBusy(false); }
    }

    function statusOf(c) {
      if (c.revoked)   return ru ? 'отозван' : 'revoked';
      if (c.expired)   return ru ? 'истёк'   : 'expired';
      if (c.exhausted) return ru ? 'исчерпан' : 'exhausted';
      return ru ? 'активен' : 'active';
    }

    return (
      <div style={{ maxWidth: 900 }}>
        <Mono c={C.ember}>· {ru ? 'СГЕНЕРИРОВАТЬ КОД' : 'GENERATE CODE'}</Mono>
        <div style={{ marginTop: 12, display: 'flex', gap: 14, alignItems: 'flex-end' }}>
          <div style={{ minWidth: 120 }}>
            <Sans s={9} c={C.dim} upper tracked>{ru ? 'Сумма, ₽' : 'Amount, ₽'}</Sans>
            <input type="number" value={amount} onChange={(e) => setAmount(e.target.value)} min="1" max="100000"
              style={{ marginTop: 4, width: '100%', background: 'transparent', border: `1px solid ${C.line}`, color: C.hi, padding: '8px 12px', fontSize: 14 }} />
          </div>
          <div style={{ minWidth: 120 }}>
            <Sans s={9} c={C.dim} upper tracked>{ru ? 'Срок, часов' : 'TTL, hours'}</Sans>
            <input type="number" value={ttl} onChange={(e) => setTtl(e.target.value)} min="1" max="720"
              style={{ marginTop: 4, width: '100%', background: 'transparent', border: `1px solid ${C.line}`, color: C.hi, padding: '8px 12px', fontSize: 14 }} />
          </div>
          <div style={{ minWidth: 120 }}>
            <Sans s={9} c={C.dim} upper tracked>{ru ? 'Активаций' : 'Max uses'}</Sans>
            <input type="number" value={maxUses} onChange={(e) => setMaxUses(e.target.value)} min="1" max="10000"
              style={{ marginTop: 4, width: '100%', background: 'transparent', border: `1px solid ${C.line}`, color: C.hi, padding: '8px 12px', fontSize: 14 }} />
          </div>
          <Btn kind="blood" onClick={gen} disabled={busy}>{busy ? '…' : (ru ? '+ Создать' : '+ Generate')}</Btn>
        </div>

        {lastCode && (
          <div style={{ marginTop: 18, padding: 18, background: 'rgba(60,140,60,0.10)', border: '1px solid #3c8c3c' }}>
            <Sans s={10} c={C.dim} upper tracked>{ru ? 'НОВЫЙ КОД (СКОПИРУЙТЕ — БОЛЬШЕ ОТКРЫТО НЕ ОТОБРАЗИТСЯ)' : 'NEW CODE (COPY NOW)'}</Sans>
            <div style={{ marginTop: 8 }}><Display s={28} c={C.paper} style={{ fontFamily: F.mono, letterSpacing: '0.18em' }}>{lastCode.code}</Display></div>
            <Sans s={11} c={C.text}>{lastCode.amount} ₽ · {lastCode.max_uses} {ru ? 'активаций' : 'uses'} · {ru ? 'до' : 'until'} {new Date(lastCode.expires_at).toLocaleString()}</Sans>
          </div>
        )}
        {err && <div style={{ marginTop: 10, padding: 8, background: 'rgba(150,30,30,0.18)' }}><Sans s={11} c={C.paper}>{err}</Sans></div>}

        <div style={{ marginTop: 28 }}>
          <Mono c={C.ember}>· {ru ? `ВЫПУЩЕННЫЕ КОДЫ (${codes.length})` : `ISSUED CODES (${codes.length})`}</Mono>
          <table style={{ width: '100%', borderCollapse: 'collapse', marginTop: 10 }}>
            <thead>
              <tr style={{ textAlign: 'left', borderBottom: `1px solid ${C.line}` }}>
                {[ru ? 'Код' : 'Code', '₽', ru ? 'Активаций' : 'Uses', ru ? 'Истекает' : 'Expires', ru ? 'Статус' : 'Status', ''].map((h, i) => (
                  <th key={i} style={{ padding: '10px 8px' }}><Sans s={9} c={C.dim} upper tracked>{h}</Sans></th>
                ))}
              </tr>
            </thead>
            <tbody>
              {codes.map((c) => (
                <tr key={c.code} style={{ borderBottom: `1px solid ${C.lineSoft}` }}>
                  <td style={{ padding: '10px 8px' }}><Mono c={C.hi}>{c.code}</Mono></td>
                  <td style={{ padding: '10px 8px' }}><Display s={14} c={C.ember}>{c.amount}</Display></td>
                  <td style={{ padding: '10px 8px' }}><Sans s={11} c={C.text}>{c.uses}/{c.max_uses}</Sans></td>
                  <td style={{ padding: '10px 8px' }}><Sans s={10} c={C.dim}>{new Date(c.expires_at).toLocaleString()}</Sans></td>
                  <td style={{ padding: '10px 8px' }}><Sans s={10} c={(c.revoked || c.expired || c.exhausted) ? C.dim : '#3c8c3c'}>{statusOf(c)}</Sans></td>
                  <td style={{ padding: '10px 8px', textAlign: 'right' }}>
                    {!c.revoked && !c.expired && !c.exhausted && (
                      <button onClick={() => revoke(c.code)} disabled={busy} style={miniBtn(C.blood, '#fff')}>{ru ? 'отозвать' : 'revoke'}</button>
                    )}
                  </td>
                </tr>
              ))}
              {codes.length === 0 && (
                <tr><td colSpan={6} style={{ padding: '20px 8px' }}><Sans s={11} c={C.dim}>{ru ? 'Кодов пока нет.' : 'No codes yet.'}</Sans></td></tr>
              )}
            </tbody>
          </table>
        </div>
      </div>
    );
  }

  // — Маленький инпут: загрузка логотипа/фавикона. Тип = 'logo' | 'favicon'.
  function SiteImagePicker({ ru, type, label, hint, currentUrl, onChange }) {
    const [busy, setBusy] = React.useState(false);
    const [err, setErr] = React.useState(null);

    async function upload(file) {
      if (!file) return;
      if (file.size > 1024 * 1024) { setErr(ru ? 'Файл больше 1 МБ' : 'File larger than 1 MB'); return; }
      const reader = new FileReader();
      reader.onload = async () => {
        setBusy(true); setErr(null);
        try {
          const r = await api('/api/admin/site-images/' + type, {
            method: 'POST',
            body: JSON.stringify({ image_data: reader.result }),
          });
          onChange(r.url);
          window.dispatchEvent(new CustomEvent('fury:site-settings'));
        } catch (e) { setErr(e.data?.error || e.message); }
        finally { setBusy(false); }
      };
      reader.readAsDataURL(file);
    }

    async function remove() {
      setBusy(true); setErr(null);
      try {
        await api('/api/admin/site-images/' + type, { method: 'DELETE' });
        onChange('');
        window.dispatchEvent(new CustomEvent('fury:site-settings'));
      } catch (e) { setErr(e.data?.error || e.message); }
      finally { setBusy(false); }
    }

    return (
      <div style={{ marginTop: 14, padding: 14, background: C.panel2, border: `1px solid ${C.line}` }}>
        <Sans s={9} c={C.dim} upper tracked>{label}</Sans>
        <div style={{ marginTop: 4 }}><Sans s={10} c={C.dim}>{hint}</Sans></div>
        <div style={{ marginTop: 10, display: 'flex', alignItems: 'flex-start', gap: 14 }}>
          <div style={{
            width: type === 'favicon' ? 64 : 140,
            height: type === 'favicon' ? 64 : 60,
            background: C.bg, border: `1px solid ${C.line}`, overflow: 'hidden', flexShrink: 0,
            display: 'flex', alignItems: 'center', justifyContent: 'center',
          }}>
            {currentUrl ? (
              <img src={currentUrl} alt="" style={{ maxWidth: '100%', maxHeight: '100%', objectFit: 'contain' }} />
            ) : <Sans s={10} c={C.dim}>{ru ? 'нет' : 'empty'}</Sans>}
          </div>
          <div style={{ flex: 1 }}>
            <input type="file"
              accept={type === 'favicon' ? 'image/png,image/svg+xml,image/x-icon,image/webp,image/jpeg' : 'image/png,image/jpeg,image/svg+xml,image/webp'}
              disabled={busy}
              onChange={(e) => upload(e.target.files && e.target.files[0])} />
            {currentUrl && (
              <button onClick={remove} disabled={busy} style={{
                display: 'block', marginTop: 8,
                background: 'transparent', border: `1px solid ${C.blood}`, color: '#fff', cursor: 'pointer',
                padding: '4px 10px', fontFamily: F.sans, fontSize: 10, letterSpacing: '0.14em', textTransform: 'uppercase',
              }}>{ru ? '× удалить' : '× remove'}</button>
            )}
            {err && <div style={{ marginTop: 8 }}><Sans s={10} c={C.blood}>{err}</Sans></div>}
          </div>
        </div>
      </div>
    );
  }

  // ── Owner: вебмейл support@furydayz.ru (IMAP+SMTP через бэкенд /api/owner/mail/*) ──
  // Layout: 3 колонки на десктопе — папки | список писем | просмотр. На узких
  // экранах сворачивается в стек. Состояние выбранной папки/письма локальное;
  // авто-помечается прочитанным при открытии (бэкенд сам ставит \Seen).
  function AdminMail({ ru }) {
    const [folders, setFolders] = React.useState([]);
    const [folder, setFolder]   = React.useState('INBOX');
    const [page, setPage]       = React.useState(1);
    const [list, setList]       = React.useState({ items: [], total: 0 });
    const [openUid, setOpenUid] = React.useState(null);
    const [msg, setMsg]         = React.useState(null);
    const [busy, setBusy]       = React.useState(false);
    const [composeOpen, setComposeOpen] = React.useState(false);
    const [composeInit, setComposeInit] = React.useState(null);
    const [err, setErr]         = React.useState(null);
    const PAGE = 25;

    const fmtAddr = (a) => a && a.address ? (a.name ? `${a.name} <${a.address}>` : a.address) : '';
    const fmtAddrShort = (a) => a && (a.name || a.address) ? (a.name || a.address) : '';
    const fmtDate = (iso) => {
      if (!iso) return '';
      const d = new Date(iso);
      const now = new Date();
      const sameDay = d.toDateString() === now.toDateString();
      if (sameDay) return d.toLocaleTimeString(ru ? 'ru-RU' : 'en-US', { hour: '2-digit', minute: '2-digit' });
      const sameYear = d.getFullYear() === now.getFullYear();
      return d.toLocaleDateString(ru ? 'ru-RU' : 'en-US', sameYear ? { day: '2-digit', month: 'short' } : { day: '2-digit', month: 'short', year: 'numeric' });
    };

    async function loadFolders() {
      try {
        const d = await api('/api/owner/mail/folders');
        setFolders(d.folders || []);
      } catch (e) { setErr((e && e.message) || 'folders_failed'); }
    }
    async function loadList(f, p) {
      setBusy(true); setErr(null);
      try {
        const d = await api(`/api/owner/mail/messages?folder=${encodeURIComponent(f)}&page=${p}&limit=${PAGE}`);
        setList(d);
      } catch (e) { setErr((e && e.message) || 'list_failed'); }
      finally { setBusy(false); }
    }
    async function openMsg(uid) {
      setOpenUid(uid); setMsg(null); setErr(null);
      setBusy(true);
      try {
        const d = await api(`/api/owner/mail/message?folder=${encodeURIComponent(folder)}&uid=${uid}`);
        setMsg(d);
        // Перерисуем список — флаг \Seen теперь стоит, цифра «непрочитано» в папке должна обновиться.
        loadFolders();
        setList((s) => ({ ...s, items: (s.items || []).map(it => it.uid === uid ? { ...it, flags: Array.from(new Set([...(it.flags || []), '\\Seen'])) } : it) }));
      } catch (e) { setErr((e && e.message) || 'open_failed'); }
      finally { setBusy(false); }
    }
    async function doMark(uid, seen) {
      try {
        await api('/api/owner/mail/mark', { method: 'POST', body: JSON.stringify({ folder, uid, seen }) });
        setList((s) => ({ ...s, items: (s.items || []).map(it => it.uid === uid ? {
          ...it, flags: seen ? Array.from(new Set([...(it.flags || []), '\\Seen'])) : (it.flags || []).filter(f => f !== '\\Seen'),
        } : it) }));
        loadFolders();
      } catch (e) { setErr((e && e.message) || 'mark_failed'); }
    }
    async function doDelete(uid) {
      if (!window.confirm(ru ? 'Удалить письмо? (переместится в Корзину)' : 'Delete message? (moves to Trash)')) return;
      try {
        await api(`/api/owner/mail/message?folder=${encodeURIComponent(folder)}&uid=${uid}`, { method: 'DELETE' });
        if (openUid === uid) { setOpenUid(null); setMsg(null); }
        loadList(folder, page);
        loadFolders();
      } catch (e) { setErr((e && e.message) || 'delete_failed'); }
    }
    function startCompose(init) { setComposeInit(init || {}); setComposeOpen(true); }
    function startReply(m) {
      const from = m.from && m.from[0];
      const subj = m.subject || '';
      const body = `\n\n\n— ${ru ? 'Исходное письмо' : 'Original message'} —\n${fmtAddr(from)}\n${m.date ? new Date(m.date).toLocaleString(ru ? 'ru-RU' : 'en-US') : ''}\n\n${m.text || ''}`;
      startCompose({
        to: from ? from.address : '',
        subject: subj.toLowerCase().startsWith('re:') ? subj : 'Re: ' + subj,
        body,
        inReplyTo: m.messageId,
        references: [].concat(m.references || []).concat(m.messageId ? [m.messageId] : []),
      });
    }
    function startForward(m) {
      const subj = m.subject || '';
      const body = `\n\n— ${ru ? 'Пересланное письмо' : 'Forwarded message'} —\n${ru ? 'От' : 'From'}: ${(m.from || []).map(fmtAddr).join(', ')}\n${ru ? 'Дата' : 'Date'}: ${m.date ? new Date(m.date).toLocaleString(ru ? 'ru-RU' : 'en-US') : ''}\n${ru ? 'Тема' : 'Subject'}: ${subj}\n${ru ? 'Кому' : 'To'}: ${(m.to || []).map(fmtAddr).join(', ')}\n\n${m.text || ''}`;
      startCompose({
        to: '',
        subject: subj.toLowerCase().startsWith('fwd:') ? subj : 'Fwd: ' + subj,
        body,
      });
    }

    React.useEffect(() => { loadFolders(); }, []);
    React.useEffect(() => { loadList(folder, page); setOpenUid(null); setMsg(null); }, [folder, page]);
    // Авто-рефреш папок раз в 30 сек на случай новых писем.
    React.useEffect(() => {
      const iv = setInterval(() => loadFolders(), 30000);
      return () => clearInterval(iv);
    }, []);

    const pages = Math.max(1, Math.ceil((list.total || 0) / PAGE));
    const curFolder = folders.find((f) => f.path === folder);

    return (
      <div>
        <div style={{ display: 'flex', alignItems: 'flex-end', gap: 14, marginBottom: 16, flexWrap: 'wrap' }}>
          <div style={{ flex: 1 }}>
            <SectionHead label={ru ? 'Почта' : 'Mail'} />
            <Sans s={11} c={C.dim} style={{ marginTop: 4 }}>support@furydayz.ru</Sans>
          </div>
          <Btn kind="blood" onClick={() => startCompose({})}>{ru ? '✉ Написать' : '✉ Compose'}</Btn>
          <Btn kind="ghost" onClick={() => { loadFolders(); loadList(folder, page); }}>{ru ? 'Обновить' : 'Refresh'}</Btn>
        </div>

        {err && (
          <div style={{ marginBottom: 14, padding: 12, border: `1px solid ${C.blood}`, background: 'rgba(168,40,40,0.10)' }}>
            <Sans s={11} c={C.hi}>{err}</Sans>
          </div>
        )}

        <div style={{ display: 'grid', gridTemplateColumns: 'minmax(180px, 220px) minmax(280px, 360px) 1fr', gap: 14, alignItems: 'start' }}>
          {/* Колонка: папки */}
          <div style={{ border: `1px solid ${C.line}`, background: C.panel2, padding: 8 }}>
            {folders.length === 0 && <Sans s={11} c={C.dim}>{ru ? 'загрузка папок…' : 'loading folders…'}</Sans>}
            {folders.map((f) => {
              const active = f.path === folder;
              return (
                <div key={f.path} onClick={() => { setFolder(f.path); setPage(1); }} style={{
                  cursor: 'pointer', padding: '8px 10px', display: 'flex', alignItems: 'center', gap: 8,
                  background: active ? C.bg : 'transparent', borderLeft: `2px solid ${active ? C.blood : 'transparent'}`,
                }}>
                  <Sans s={11} c={active ? C.hi : C.text} style={{ flex: 1, minWidth: 0, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{f.label}</Sans>
                  {f.unseen > 0 && (
                    <span style={{ background: C.ember, color: C.bg, borderRadius: 10, padding: '1px 7px', fontSize: 10, fontWeight: 600, fontFamily: F.sans, flexShrink: 0 }}>{f.unseen}</span>
                  )}
                </div>
              );
            })}
          </div>

          {/* Колонка: список писем */}
          <div style={{ border: `1px solid ${C.line}`, background: C.panel2, minHeight: 400 }}>
            <div style={{ padding: '10px 12px', borderBottom: `1px solid ${C.line}`, display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: 8, flexWrap: 'wrap' }}>
              <Sans s={11} c={C.hi} upper tracked>{curFolder ? curFolder.label : folder}</Sans>
              {pages > 1 && (
                <div style={{ display: 'flex', gap: 6, alignItems: 'center' }}>
                  <button onClick={() => setPage(Math.max(1, page - 1))} disabled={page <= 1} style={{ background: 'transparent', border: `1px solid ${C.line}`, color: C.hi, padding: '3px 8px', cursor: page <= 1 ? 'not-allowed' : 'pointer', opacity: page <= 1 ? 0.4 : 1, fontFamily: F.sans, fontSize: 10 }}>←</button>
                  <Sans s={10} c={C.dim}>{page} / {pages}</Sans>
                  <button onClick={() => setPage(Math.min(pages, page + 1))} disabled={page >= pages} style={{ background: 'transparent', border: `1px solid ${C.line}`, color: C.hi, padding: '3px 8px', cursor: page >= pages ? 'not-allowed' : 'pointer', opacity: page >= pages ? 0.4 : 1, fontFamily: F.sans, fontSize: 10 }}>→</button>
                </div>
              )}
            </div>
            {busy && !list.items.length && <div style={{ padding: 16 }}><Sans s={11} c={C.dim}>{ru ? 'Загрузка…' : 'Loading…'}</Sans></div>}
            {!busy && list.items && list.items.length === 0 && <div style={{ padding: 16 }}><Sans s={11} c={C.dim}>{ru ? 'Пусто' : 'Empty'}</Sans></div>}
            <div style={{ maxHeight: 600, overflowY: 'auto' }}>
              {(list.items || []).map((it) => {
                const unread = !(it.flags || []).includes('\\Seen');
                const fromAddr = (it.from && it.from[0]) || null;
                const isSel = openUid === it.uid;
                const isSentFolder = curFolder && curFolder.specialUse === '\\Sent';
                const headerAddr = isSentFolder
                  ? ((it.to && it.to[0]) || fromAddr)
                  : fromAddr;
                return (
                  <div key={it.uid} onClick={() => openMsg(it.uid)} style={{
                    cursor: 'pointer', padding: '10px 12px',
                    borderBottom: `1px solid ${C.lineSoft}`,
                    background: isSel ? C.bg : 'transparent',
                    borderLeft: `3px solid ${unread ? C.ember : 'transparent'}`,
                  }}>
                    <div style={{ display: 'flex', justifyContent: 'space-between', gap: 8, alignItems: 'baseline' }}>
                      <Sans s={11} c={unread ? C.hi : C.text} style={{ fontWeight: unread ? 600 : 400, flex: 1, minWidth: 0, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
                        {fmtAddrShort(headerAddr) || (ru ? '(без отправителя)' : '(no sender)')}
                      </Sans>
                      <Sans s={10} c={C.dim} style={{ flexShrink: 0 }}>{fmtDate(it.date)}</Sans>
                    </div>
                    <div style={{ marginTop: 3, display: 'flex', gap: 6, alignItems: 'center' }}>
                      {it.hasAttachments && <span style={{ color: C.ember, fontSize: 11, flexShrink: 0 }}>📎</span>}
                      <Sans s={11} c={unread ? C.hi : C.text} style={{ fontWeight: unread ? 500 : 400, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
                        {it.subject || (ru ? '(без темы)' : '(no subject)')}
                      </Sans>
                    </div>
                  </div>
                );
              })}
            </div>
          </div>

          {/* Колонка: просмотр */}
          <div style={{ border: `1px solid ${C.line}`, background: C.panel2, minHeight: 400 }}>
            {!openUid && <div style={{ padding: 20 }}><Sans s={11} c={C.dim}>{ru ? '← выберите письмо' : '← pick a message'}</Sans></div>}
            {openUid && !msg && <div style={{ padding: 20 }}><Sans s={11} c={C.dim}>{ru ? 'Загрузка…' : 'Loading…'}</Sans></div>}
            {msg && (
              <MailDetail ru={ru} msg={msg} folder={folder}
                onReply={() => startReply(msg)}
                onForward={() => startForward(msg)}
                onDelete={() => doDelete(msg.uid)}
                onMarkUnread={() => doMark(msg.uid, false)}
              />
            )}
          </div>
        </div>

        {composeOpen && (
          <MailCompose ru={ru} init={composeInit || {}}
            onClose={() => setComposeOpen(false)}
            onSent={() => { setComposeOpen(false); loadFolders(); /* Yandex сам кладёт в Sent */ }}
          />
        )}
      </div>
    );
  }

  // — Просмотр одного письма.
  function MailDetail({ ru, msg, folder, onReply, onForward, onDelete, onMarkUnread }) {
    const [iframeH, setIframeH] = React.useState(200);
    const ifr = React.useRef(null);
    const fmtAddr = (a) => a && a.address ? (a.name ? `${a.name} <${a.address}>` : a.address) : '';
    const downloadAtt = (a) => {
      const url = `/api/owner/mail/attachment?folder=${encodeURIComponent(folder)}&uid=${msg.uid}&part=${a.partIdx}`;
      window.open(url, '_blank');
    };
    // HTML письмо рендерим в iframe sandbox — изоляция от страницы (XSS-защита).
    const srcDoc = msg.html
      ? `<!doctype html><meta charset="utf-8"><base target="_blank"><style>html,body{margin:0;padding:14px;background:#fff;color:#222;font:14px/1.5 system-ui,Inter,Arial,sans-serif;}img{max-width:100%;height:auto}a{color:#0066cc}table{max-width:100%}</style>${msg.html}`
      : null;

    React.useEffect(() => {
      if (!ifr.current || !srcDoc) return;
      const onLoad = () => {
        try {
          const doc = ifr.current.contentDocument;
          if (doc && doc.body) setIframeH(Math.min(900, Math.max(180, doc.body.scrollHeight + 24)));
        } catch (_) {}
      };
      ifr.current.addEventListener('load', onLoad);
      return () => { try { ifr.current && ifr.current.removeEventListener('load', onLoad); } catch (_) {} };
    }, [srcDoc]);

    return (
      <div style={{ display: 'flex', flexDirection: 'column', maxHeight: 720 }}>
        <div style={{ padding: '12px 16px', borderBottom: `1px solid ${C.line}` }}>
          <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: 10, marginBottom: 8 }}>
            <Display s={16} w={500} style={{ flex: 1, minWidth: 0 }}>{msg.subject || (ru ? '(без темы)' : '(no subject)')}</Display>
            <Sans s={10} c={C.dim} style={{ flexShrink: 0 }}>{msg.date ? new Date(msg.date).toLocaleString(ru ? 'ru-RU' : 'en-US') : ''}</Sans>
          </div>
          <div><Sans s={11} c={C.dim}>{ru ? 'От' : 'From'}: </Sans><Sans s={11} c={C.hi}>{(msg.from || []).map(fmtAddr).join(', ') || '—'}</Sans></div>
          <div><Sans s={11} c={C.dim}>{ru ? 'Кому' : 'To'}: </Sans><Sans s={11} c={C.text}>{(msg.to || []).map(fmtAddr).join(', ') || '—'}</Sans></div>
          {msg.cc && msg.cc.length > 0 && <div><Sans s={11} c={C.dim}>Cc: </Sans><Sans s={11} c={C.text}>{msg.cc.map(fmtAddr).join(', ')}</Sans></div>}
          <div style={{ marginTop: 10, display: 'flex', gap: 8, flexWrap: 'wrap' }}>
            <button onClick={onReply}     style={{ background: 'transparent', border: `1px solid ${C.blood}`, color: C.blood, padding: '5px 12px', cursor: 'pointer', fontFamily: F.sans, fontSize: 10, textTransform: 'uppercase', letterSpacing: '0.1em' }}>{ru ? '↩ Ответить' : '↩ Reply'}</button>
            <button onClick={onForward}   style={{ background: 'transparent', border: `1px solid ${C.line}`, color: C.hi,    padding: '5px 12px', cursor: 'pointer', fontFamily: F.sans, fontSize: 10, textTransform: 'uppercase', letterSpacing: '0.1em' }}>{ru ? '→ Переслать' : '→ Forward'}</button>
            <button onClick={onMarkUnread} style={{ background: 'transparent', border: `1px solid ${C.line}`, color: C.dim,   padding: '5px 12px', cursor: 'pointer', fontFamily: F.sans, fontSize: 10, textTransform: 'uppercase', letterSpacing: '0.1em' }}>{ru ? 'Не прочитано' : 'Mark unread'}</button>
            <button onClick={onDelete}     style={{ background: 'transparent', border: `1px solid ${C.line}`, color: '#d06868', padding: '5px 12px', cursor: 'pointer', fontFamily: F.sans, fontSize: 10, textTransform: 'uppercase', letterSpacing: '0.1em' }}>{ru ? '✕ Удалить' : '✕ Delete'}</button>
          </div>
        </div>

        {msg.attachments && msg.attachments.length > 0 && (
          <div style={{ padding: '10px 16px', borderBottom: `1px solid ${C.line}`, background: C.bg }}>
            <Sans s={9} c={C.dim} upper tracked>{ru ? 'Вложения' : 'Attachments'} · {msg.attachments.length}</Sans>
            <div style={{ marginTop: 8, display: 'flex', flexWrap: 'wrap', gap: 8 }}>
              {msg.attachments.map((a) => (
                <button key={a.partIdx} onClick={() => downloadAtt(a)} style={{
                  display: 'flex', alignItems: 'center', gap: 8, padding: '8px 12px',
                  border: `1px solid ${C.line}`, background: C.panel2, color: C.hi,
                  cursor: 'pointer', fontFamily: F.sans, fontSize: 11, maxWidth: 280,
                }}>
                  <span style={{ color: C.ember }}>📎</span>
                  <span style={{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', flex: 1 }}>{a.filename}</span>
                  <span style={{ color: C.dim, fontSize: 10, flexShrink: 0 }}>{a.size ? Math.round(a.size / 1024) + 'k' : ''}</span>
                </button>
              ))}
            </div>
          </div>
        )}

        <div style={{ padding: srcDoc ? 0 : '16px', overflow: 'auto', flex: 1 }}>
          {srcDoc ? (
            <iframe ref={ifr} sandbox="allow-same-origin" srcDoc={srcDoc}
              style={{ border: 'none', width: '100%', height: iframeH, background: '#fff' }} />
          ) : (
            <pre style={{ margin: 0, fontFamily: F.sans, fontSize: 13, color: C.text, whiteSpace: 'pre-wrap', wordBreak: 'break-word' }}>{msg.text || (ru ? '(пусто)' : '(empty)')}</pre>
          )}
        </div>
      </div>
    );
  }

  // — Модалка «Написать письмо». Поддерживает To/CC/Subject/Body + вложения (base64).
  function MailCompose({ ru, init, onClose, onSent }) {
    const [to,   setTo]   = React.useState(init.to || '');
    const [cc,   setCc]   = React.useState(init.cc || '');
    const [subj, setSubj] = React.useState(init.subject || '');
    const [body, setBody] = React.useState(init.body || '');
    const [atts, setAtts] = React.useState([]); // {filename, contentType, size, content_base64}
    const [busy, setBusy] = React.useState(false);
    const [err, setErr]   = React.useState(null);
    const fileRef = React.useRef(null);

    const inp = { width: '100%', boxSizing: 'border-box', background: 'transparent', border: `1px solid ${C.line}`, color: C.hi, padding: '8px 10px', fontFamily: F.sans, fontSize: 12 };

    function onFilesPicked(e) {
      const fs = Array.from(e.target.files || []);
      Promise.all(fs.map((f) => new Promise((resolve, reject) => {
        const r = new FileReader();
        r.onerror = reject;
        r.onload  = () => {
          const dataUrl = r.result; // "data:mime;base64,...."
          const m = String(dataUrl).match(/^data:([^;]+);base64,(.+)$/);
          if (!m) return reject(new Error('bad_file'));
          resolve({ filename: f.name, contentType: m[1], size: f.size, content_base64: m[2] });
        };
        r.readAsDataURL(f);
      }))).then((arr) => {
        setAtts((prev) => prev.concat(arr));
        if (fileRef.current) fileRef.current.value = '';
      }).catch((e) => setErr(e.message));
    }
    function removeAtt(i) { setAtts((prev) => prev.filter((_, k) => k !== i)); }

    async function send() {
      setErr(null);
      const toList = to.split(/[,;]/).map((s) => s.trim()).filter(Boolean);
      if (!toList.length) { setErr(ru ? 'Укажите получателя' : 'Recipient required'); return; }
      setBusy(true);
      try {
        await api('/api/owner/mail/send', {
          method: 'POST',
          body: JSON.stringify({
            to: toList,
            cc: cc ? cc.split(/[,;]/).map((s) => s.trim()).filter(Boolean) : undefined,
            subject: subj || '(no subject)',
            text: body,
            attachments: atts,
            inReplyTo: init.inReplyTo,
            references: init.references,
          }),
        });
        onSent && onSent();
      } catch (e) { setErr((e && e.message) || 'send_failed'); setBusy(false); }
    }

    const overlay = { position: 'fixed', inset: 0, background: 'rgba(13,10,8,0.85)', zIndex: 1100, display: 'flex', alignItems: 'center', justifyContent: 'center', padding: 20 };
    const box = { background: C.bg, border: `1px solid ${C.line}`, width: 'min(720px, 100%)', maxHeight: '92vh', overflow: 'auto', padding: 24 };

    React.useEffect(() => {
      const onKey = (e) => { if (e.key === 'Escape') onClose(); };
      window.addEventListener('keydown', onKey);
      return () => window.removeEventListener('keydown', onKey);
    }, []);

    return (
      <div style={overlay}>
        <div style={box}>
          <div style={{ display: 'flex', alignItems: 'center', marginBottom: 18 }}>
            <Display s={20} w={500}>{ru ? 'Новое письмо' : 'New message'}</Display>
            <div style={{ flex: 1 }} />
            <button onClick={onClose} style={{ background: 'transparent', border: 'none', cursor: 'pointer', color: C.dim, fontSize: 22 }}>×</button>
          </div>

          <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
            <div>
              <Sans s={9} c={C.dim} upper tracked>{ru ? 'Кому' : 'To'}</Sans>
              <input value={to} onChange={(e) => setTo(e.target.value)} placeholder="user@example.com, another@example.com" style={inp} />
            </div>
            <div>
              <Sans s={9} c={C.dim} upper tracked>Cc</Sans>
              <input value={cc} onChange={(e) => setCc(e.target.value)} placeholder={ru ? 'опционально' : 'optional'} style={inp} />
            </div>
            <div>
              <Sans s={9} c={C.dim} upper tracked>{ru ? 'Тема' : 'Subject'}</Sans>
              <input value={subj} onChange={(e) => setSubj(e.target.value)} style={inp} />
            </div>
            <div>
              <Sans s={9} c={C.dim} upper tracked>{ru ? 'Текст' : 'Body'}</Sans>
              <textarea value={body} onChange={(e) => setBody(e.target.value)} rows={12} style={{ ...inp, fontFamily: F.sans, resize: 'vertical' }} />
            </div>

            <div>
              <Sans s={9} c={C.dim} upper tracked>{ru ? 'Вложения' : 'Attachments'}</Sans>
              <div style={{ marginTop: 6, display: 'flex', alignItems: 'center', gap: 10, flexWrap: 'wrap' }}>
                <input ref={fileRef} type="file" multiple onChange={onFilesPicked} style={{ display: 'none' }} />
                <button onClick={() => fileRef.current && fileRef.current.click()} style={{
                  background: 'transparent', border: `1px solid ${C.line}`, color: C.hi,
                  padding: '6px 14px', cursor: 'pointer', fontFamily: F.sans, fontSize: 11,
                  letterSpacing: '0.08em', textTransform: 'uppercase',
                }}>📎 {ru ? 'Выбрать файлы' : 'Choose files'}</button>
                <Sans s={10} c={C.dim}>{ru ? 'до 16 МБ суммарно' : 'up to 16 MB total'}</Sans>
              </div>
              {atts.length > 0 && (
                <div style={{ marginTop: 8, display: 'flex', flexWrap: 'wrap', gap: 6 }}>
                  {atts.map((a, i) => (
                    <div key={i} style={{ display: 'flex', alignItems: 'center', gap: 8, padding: '6px 10px', background: C.panel2, border: `1px solid ${C.line}`, maxWidth: 280 }}>
                      <span style={{ color: C.ember, fontSize: 11 }}>📎</span>
                      <span style={{ color: C.hi, fontSize: 11, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', flex: 1 }}>{a.filename}</span>
                      <span style={{ color: C.dim, fontSize: 10, flexShrink: 0 }}>{Math.round(a.size / 1024)}k</span>
                      <button onClick={() => removeAtt(i)} style={{ background: 'transparent', border: 'none', color: '#d06868', cursor: 'pointer', fontSize: 14, padding: 0 }}>×</button>
                    </div>
                  ))}
                </div>
              )}
            </div>
          </div>

          {err && (
            <div style={{ marginTop: 14, padding: 10, border: `1px solid ${C.blood}`, background: 'rgba(168,40,40,0.10)' }}>
              <Sans s={11} c={C.hi}>{err}</Sans>
            </div>
          )}

          <div style={{ display: 'flex', justifyContent: 'flex-end', gap: 10, marginTop: 16 }}>
            <Btn kind="ghost" onClick={onClose} disabled={busy}>{ru ? 'Отмена' : 'Cancel'}</Btn>
            <Btn kind="blood" onClick={send} disabled={busy || !to.trim()}>{busy ? '…' : (ru ? 'Отправить' : 'Send')}</Btn>
          </div>
        </div>
      </div>
    );
  }

  // — Редактирование содержимого сайта (футер-ссылки + 3 политики). Owner-only.
  function AdminContent({ ru }) {
    const [s, setS] = React.useState(null);
    const [busy, setBusy] = React.useState(false);
    const [msg, setMsg] = React.useState(null);
    const [err, setErr] = React.useState(null);

    async function reload() {
      try {
        const r = await api('/api/site-settings');
        setS(r.settings || {});
      } catch (e) { setErr(e.message); }
    }
    React.useEffect(() => { reload(); }, []);

    if (!s) return <Sans s={11} c={C.dim}>{ru ? 'Загрузка…' : 'Loading…'}</Sans>;

    const set = (k) => (e) => setS({ ...s, [k]: e.target.value });

    async function save() {
      setBusy(true); setMsg(null); setErr(null);
      try {
        await api('/api/admin/site-settings', { method: 'PATCH', body: JSON.stringify(s) });
        // Обновим кеш в window для футера.
        window.__FURY_SITE_SETTINGS = { ...s };
        setMsg(ru ? 'Сохранено' : 'Saved');
      } catch (e) { setErr(e.data?.error || e.message); }
      finally { setBusy(false); }
    }

    const labelStyle = { display: 'block', marginTop: 18 };
    const taStyle = {
      marginTop: 6, width: '100%', minHeight: 180, background: 'transparent',
      border: `1px solid ${C.line}`, color: C.hi, padding: 12,
      fontFamily: F.sans, fontSize: 13, lineHeight: 1.5, resize: 'vertical',
    };
    const inputStyle = {
      marginTop: 6, width: '100%', background: 'transparent',
      border: `1px solid ${C.line}`, color: C.hi, padding: '10px 14px',
      fontFamily: F.mono, fontSize: 13,
    };

    return (
      <div style={{ maxWidth: 880 }}>
        <Sans s={11} c={C.dim}>{ru
          ? 'Эти поля видны всем посетителям сайта в подвале и в модальных окнах с политиками. Сохранение перезапишет текущие значения сразу для всех.'
          : 'These fields are public — they show in the site footer and policy modals. Save updates them for all visitors immediately.'}</Sans>

        {/* Логотип в шапке + фавикон вкладки. Сохраняются мгновенно при выборе файла. */}
        <div style={{ marginTop: 22 }}>
          <Mono c={C.ember}>· {ru ? 'ЛОГОТИП И ИКОНКА ВКЛАДКИ' : 'LOGO & FAVICON'}</Mono>
          <SiteImagePicker ru={ru} type="logo" label={ru ? 'Логотип в шапке' : 'Header logo'}
            hint={ru
              ? 'PNG/JPG/WebP/SVG, до 1 МБ. Заменяет крест-иконку слева от слова FURY. Высота 50px, ширина — автоматически.'
              : 'PNG/JPG/WebP/SVG, up to 1 MB. Replaces the cross icon next to the FURY wordmark.'}
            currentUrl={s.logo_url}
            onChange={(url) => setS({ ...s, logo_url: url })} />
          <SiteImagePicker ru={ru} type="favicon" label={ru ? 'Иконка вкладки браузера (favicon)' : 'Browser tab icon (favicon)'}
            hint={ru
              ? 'PNG/SVG/ICO/WebP, до 1 МБ. Желательно квадратное изображение 32×32 или 64×64.'
              : 'PNG/SVG/ICO/WebP, up to 1 MB. Square 32×32 or 64×64 is ideal.'}
            currentUrl={s.favicon_url}
            onChange={(url) => setS({ ...s, favicon_url: url })} />
        </div>

        <div style={{ marginTop: 28 }}>
          <Mono c={C.ember}>· {ru ? 'ССЫЛКИ В ПОДВАЛЕ' : 'FOOTER LINKS'}</Mono>
        </div>

        <label style={labelStyle}>
          <Sans s={9} c={C.dim} upper tracked>Discord URL</Sans>
          <input value={s.discord_url || ''} onChange={set('discord_url')} placeholder="https://discord.gg/..." style={inputStyle} />
        </label>

        <label style={labelStyle}>
          <Sans s={9} c={C.dim} upper tracked>VK URL</Sans>
          <input value={s.vk_url || ''} onChange={set('vk_url')} placeholder="https://vk.com/..." style={inputStyle} />
        </label>

        <label style={labelStyle}>
          <Sans s={9} c={C.dim} upper tracked>Telegram URL</Sans>
          <input value={s.tg_url || ''} onChange={set('tg_url')} placeholder="https://t.me/..." style={inputStyle} />
        </label>

        <label style={labelStyle}>
          <Sans s={9} c={C.dim} upper tracked>YouTube URL</Sans>
          <input value={s.youtube_url || ''} onChange={set('youtube_url')} placeholder="https://youtube.com/@..." style={inputStyle} />
        </label>

        <label style={labelStyle}>
          <Sans s={9} c={C.dim} upper tracked>{ru ? 'Ссылка на мод в Steam' : 'Steam Workshop mod URL'}</Sans>
          <input value={s.mod_url || ''} onChange={set('mod_url')} placeholder="https://steamcommunity.com/sharedfiles/filedetails/?id=..." style={inputStyle} />
        </label>

        <label style={labelStyle}>
          <Sans s={9} c={C.dim} upper tracked>{ru ? 'Пользовательское соглашение' : 'Terms of Use'}</Sans>
          <textarea value={s.policy_terms || ''} onChange={set('policy_terms')} style={taStyle} />
        </label>

        <label style={labelStyle}>
          <Sans s={9} c={C.dim} upper tracked>{ru ? 'Политика конфиденциальности' : 'Privacy Policy'}</Sans>
          <textarea value={s.policy_privacy || ''} onChange={set('policy_privacy')} style={taStyle} />
        </label>

        <label style={labelStyle}>
          <Sans s={9} c={C.dim} upper tracked>{ru ? 'Политика информационной безопасности' : 'Information Security Policy'}</Sans>
          <textarea value={s.policy_security || ''} onChange={set('policy_security')} style={taStyle} />
        </label>

        {err && <div style={{ marginTop: 14, padding: 10, background: 'rgba(150,30,30,0.18)' }}><Sans s={11} c={C.paper}>{err}</Sans></div>}
        {msg && <div style={{ marginTop: 14, padding: 10, background: 'rgba(60,140,60,0.18)' }}><Sans s={11} c={C.paper}>{msg}</Sans></div>}

        <div style={{ marginTop: 22, display: 'flex' }}>
          <div style={{ flex: 1 }} />
          <Btn kind="blood" onClick={save} disabled={busy}>{busy ? '…' : (ru ? 'Сохранить' : 'Save')}</Btn>
        </div>
      </div>
    );
  }

  function Btn({ kind = 'blood', children, onClick, full, disabled, style }) {
    const map = {
      blood: { bg: C.blood, fg: '#fff', bd: 'transparent' },
      cream: { bg: C.paper, fg: C.bg, bd: 'transparent' },
      ghost: { bg: 'transparent', fg: C.hi, bd: C.line },
      link:  { bg: 'transparent', fg: C.ember, bd: 'transparent' },
    };
    const v = map[kind];
    return (
      <button onClick={onClick} disabled={disabled} style={{
        background: v.bg, color: v.fg, border: `1px solid ${v.bd}`,
        padding: '12px 22px', fontFamily: F.sans, fontSize: 11,
        letterSpacing: '0.18em', textTransform: 'uppercase', fontWeight: 600,
        cursor: disabled ? 'not-allowed' : 'pointer', opacity: disabled ? 0.5 : 1,
        width: full ? '100%' : 'auto', transition: 'all .15s', ...style,
      }}>{children}</button>
    );
  }

  // Atmospheric card image — full-bleed gradient w/ silhouette glyph + faint
  // grain. Wider than other variants.
  function CardArt({ p, h = 240, withVignette = true }) {
    const hasImage = !!p.image_url;
    // Когда есть пользовательская картинка — наложения снижаем чтобы не «убивать» фото.
    // Когда картинки нет — оставляем глубокий cinematic tint + силуэт.
    const fogOpacity      = hasImage ? 0.30 : 0.7;
    const vignetteOpacity = hasImage ? 0.28 : 0.6;
    const grainOpacity    = hasImage ? 0.08 : 0.18;
    return (
      <div className="fury-card-art" style={{
        position: 'relative', height: h, overflow: 'hidden',
        background: hasImage ? '#0d0a08' : furyTone(p.tone),
      }}>
        {/* user-uploaded image, если есть — целиком вписана в слот (object-fit: contain),
            пустые области по краям остаются тёмным фоном карточки.
            Transform/transition берём из глобального CSS (.fury-card-img). */}
        {hasImage && (
          <img className="fury-card-img" src={p.image_url} alt="" style={{
            position: 'absolute', inset: 0, width: '100%', height: '100%',
            objectFit: 'contain', display: 'block',
          }} />
        )}
        {/* fog снизу */}
        <div style={{
          position: 'absolute', inset: 0, pointerEvents: 'none',
          background: `radial-gradient(ellipse at 50% 100%, rgba(13,10,8,${fogOpacity}) 0%, transparent 60%)`,
        }} />
        {/* grain */}
        <div style={{
          position: 'absolute', inset: 0, pointerEvents: 'none',
          opacity: grainOpacity, mixBlendMode: 'overlay',
          backgroundImage:
            "url(\"data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='160' height='160'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/></filter><rect width='100%' height='100%' filter='url(%23n)' opacity='0.7'/></svg>\")",
        }} />
        {/* silhouette — показываем только если нет загруженной картинки */}
        {!hasImage && (
          <div style={{
            position: 'absolute', inset: 0, display: 'flex', alignItems: 'center', justifyContent: 'center',
            color: 'rgba(232,220,203,0.78)',
          }}>
            <FuryGlyph cat={p.cat} size={Math.round(h * 0.45)} />
          </div>
        )}
        {withVignette && (
          <div style={{
            position: 'absolute', inset: 0, pointerEvents: 'none',
            background:
              `radial-gradient(ellipse at center, transparent 50%, rgba(13,10,8,${vignetteOpacity}) 100%)`,
          }} />
        )}
      </div>
    );
  }

  function Badge({ kind, t }) {
    if (!kind) return null;
    const m = {
      hot:     { ribbon: C.blood, fg: '#fff', text: t.hot_badge },
      new:     { ribbon: C.ember, fg: '#1a1208', text: t.new_badge },
      vip:     { ribbon: C.paper, fg: C.bg, text: t.vip_badge },
      limited: { ribbon: C.bloodDeep, fg: '#fff', text: t.limited },
    };
    const v = m[kind] || m.hot;
    return (
      <div style={{
        position: 'absolute', top: 16, left: 0, zIndex: 2,
        background: v.ribbon, color: v.fg, padding: '6px 14px 6px 16px',
        fontFamily: F.sans, fontSize: 10, fontWeight: 600,
        letterSpacing: '0.2em', textTransform: 'uppercase',
      }}>{v.text}</div>
    );
  }

  // ── Product card ─────────────────────────────────────────────────────────
  function ProductCard({ p, inCart, onAdd, setQty, onOpen, t, lang, index = 0 }) {
    // Категория — динамическая из API, фоллбек на t.cats (для старых сборок).
    const dynCats = window.FURY_CATEGORIES || [];
    const catRow = dynCats.find((x) => x.id === p.cat);
    const catLabel = catRow ? (lang === 'ru' ? catRow.name_ru : catRow.name_en) : (t.cats[p.cat] || p.cat);
    return (
      <article
        className="fury-card fury-grid-item"
        style={{
          background: C.panel, position: 'relative', overflow: 'hidden',
          boxShadow: `inset 0 0 0 1px ${C.lineSoft}`,
          cursor: 'pointer',
          animationDelay: Math.min(index * 35, 480) + 'ms',
        }}
        onClick={() => onOpen(p.id)}>
        <Badge kind={p.badge} t={t} compact />
        {/* Высота картинки больше — теперь занимает основную долю карточки. */}
        <CardArt p={p} h={190} />
        <div style={{ padding: '8px 12px 10px', position: 'relative' }}>
          {/* price chip — top-right, overlapping */}
          <div style={{
            position: 'absolute', right: 8, top: -16,
            background: C.bg, padding: '4px 8px',
            border: `1px solid ${C.line}`, textAlign: 'right',
          }}>
            {(() => {
              const eff = window.furyEffectivePrice(p);
              const onDeal = eff !== p.price;
              const strike = onDeal ? p.price : p.was;
              return (<>
                {strike && <Sans s={8} c={C.dim} style={{ textDecoration: 'line-through', display: 'block', lineHeight: 1 }}>{furyFmt(strike)} {t.rub}</Sans>}
                <Display s={14} c={onDeal ? C.blood : C.hi} w={500} style={{ lineHeight: 1.05 }}>{furyFmt(eff)} <span style={{ fontSize: 8, color: C.dim }}>{t.rub}</span></Display>
              </>);
            })()}
          </div>
          <Mono c={C.ember} style={{ fontSize: 7, letterSpacing: '0.18em', lineHeight: 1 }}>· {catLabel}</Mono>
          <div style={{ marginTop: 2 }}>
            <Display s={13} italic style={{ lineHeight: 1.1 }}>{window.FURY_pname(p)}</Display>
          </div>
          <div style={{ marginTop: 7 }}>
            {inCart ? (
              // В корзине — степпер. Минус при qty=1 удаляет из корзины (setQty с 0).
              <div onClick={(e) => e.stopPropagation()}
                style={{
                  display: 'flex', alignItems: 'stretch',
                  border: `1px solid ${C.line}`, height: 28,
                }}>
                <button
                  onClick={() => setQty(p.id, inCart - 1)}
                  style={{
                    background: 'transparent', border: 'none', color: C.hi, cursor: 'pointer',
                    width: 34, fontFamily: F.serif, fontSize: 18, lineHeight: 1,
                  }}>−</button>
                <div style={{
                  flex: 1, textAlign: 'center', alignSelf: 'center',
                  fontFamily: F.sans, fontSize: 12, color: C.hi, letterSpacing: '0.1em',
                }}>{inCart}</div>
                {p.cat !== 'slots' && (
                <button
                  onClick={() => setQty(p.id, inCart + 1)}
                  style={{
                    background: 'transparent', border: 'none', color: C.hi, cursor: 'pointer',
                    width: 34, fontFamily: F.serif, fontSize: 16, lineHeight: 1,
                  }}>+</button>
                )}
              </div>
            ) : (
              <Btn kind="blood" full
                onClick={(e) => { e.stopPropagation(); onAdd(p.id); }}
                style={{ padding: '6px 10px', fontSize: 9, letterSpacing: '0.14em' }}>
                <span className="fury-buy-btn" style={{ display: 'inline-block' }}>
                  + {t.add_to_cart}
                </span>
              </Btn>
            )}
          </div>
        </div>
      </article>
    );
  }

  // ── Header ───────────────────────────────────────────────────────────────
  function Header({ shop }) {
    const { t, view, setView, cartCount, setCartOpen, profile, lang } = shop;
    const [topupOpen, setTopupOpen] = React.useState(false);
    const siteSettings = useSiteSettings();
    const vp = useViewport();
    const logoUrl = siteSettings && siteSettings.logo_url;
    return (
      <header style={{
        minHeight: vp.isMobile ? 'auto' : 88,
        background: C.bg, borderBottom: `1px solid ${C.lineSoft}`,
        display: 'flex', alignItems: 'center',
        padding: vp.isMobile ? '10px 12px' : '0 36px',
        gap: vp.isMobile ? 10 : 32,
        flexWrap: vp.isMobile ? 'wrap' : 'nowrap',
      }}>
        {topupOpen && (
          <TopUpModal lang={lang} onClose={() => setTopupOpen(false)} loggedIn={profile.loggedIn !== false} />
        )}
        {/* Logo: serif wordmark with hairline cross above */}
        <button onClick={() => setView('shop')} title={lang === 'ru' ? 'В магазин' : 'To shop'} style={{
          display: 'flex', alignItems: 'center', gap: 14,
          background: 'transparent', border: 'none', padding: 0, cursor: 'pointer', color: 'inherit',
        }}>
          {logoUrl ? (
            <img src={logoUrl} alt="FURY" style={{ height: vp.isMobile ? 36 : 50, width: 'auto', maxWidth: vp.isMobile ? 80 : 120, objectFit: 'contain', display: 'block' }} />
          ) : (
            <div style={{ position: 'relative', width: 40, height: 50 }}>
              <span style={{ position: 'absolute', top: 6,  left: '50%', width: 1,  height: 38, background: C.blood, transform: 'translateX(-50%)' }} />
              <span style={{ position: 'absolute', top: 18, left: '50%', width: 22, height: 1,  background: C.blood, transform: 'translateX(-50%)' }} />
              <span style={{ position: 'absolute', bottom: 0, left: '50%', width: 8, height: 8, background: C.blood, transform: 'translateX(-50%) rotate(45deg)' }} />
            </div>
          )}
          <div style={{ textAlign: 'left' }}>
            <Display s={vp.isMobile ? 20 : 28} w={600} style={{ letterSpacing: '0.04em' }}>{t.brand}</Display>
            {!vp.isMobile && (<div style={{ marginTop: 2 }}><Sans s={9} c={C.dim} upper tracked>— {t.tagline}</Sans></div>)}
          </div>
        </button>

        <div style={{ flex: 1 }} />

        <nav style={{ display: 'flex', gap: vp.isMobile ? 14 : 28 }}>
          {(() => {
            const items = [['shop', t.nav_shop], ['profile', t.nav_profile]];
            const role = profile && profile.role;
            if (role === 'admin' || role === 'owner') {
              items.push(['admin', lang === 'ru' ? 'Админка' : 'Admin']);
            }
            if (role === 'owner') {
              items.push(['mail', lang === 'ru' ? 'Почта' : 'Mail']);
            }
            return items;
          })().map(([k, label]) => {
            const active = view === k;
            return (
              <button key={k} onClick={() => setView(k)} style={{
                background: 'transparent', border: 'none', cursor: 'pointer',
                padding: '6px 0', position: 'relative',
                fontFamily: F.sans, fontSize: vp.isMobile ? 10 : 11,
                letterSpacing: vp.isMobile ? '0.08em' : '0.2em',
                textTransform: 'uppercase', fontWeight: 500,
                color: active ? C.hi : C.dim,
              }}>
                {label}
                {active && (
                  <span style={{
                    position: 'absolute', left: 0, right: 0, bottom: -4,
                    height: 1, background: C.blood,
                  }} />
                )}
              </button>
            );
          })}
        </nav>

        <div style={{ flex: 1 }} />

        {/* Search */}
        <div style={{
          display: 'flex', alignItems: 'center', gap: 10,
          borderBottom: `1px solid ${C.line}`, padding: '6px 4px',
          width: vp.isMobile ? '100%' : 200,
          order: vp.isMobile ? 10 : 0,
        }}>
          <span style={{ color: C.dim, fontFamily: F.serif, fontStyle: 'italic' }}>⌕</span>
          <input value={shop.search} onChange={(e) => shop.setSearch(e.target.value)}
            placeholder={t.search}
            style={{
              flex: 1, background: 'transparent', border: 'none', outline: 'none',
              color: C.hi, fontFamily: F.serif, fontStyle: 'italic', fontSize: 14,
            }} />
        </div>

        {/* Balance — кликабельный, открывает модал пополнения через ЮKassa/Paypalych */}
        <button
          onClick={() => setTopupOpen(true)}
          title={lang === 'ru' ? 'Пополнить баланс' : 'Top up balance'}
          style={{
            textAlign: 'right', background: 'transparent', border: 'none', cursor: 'pointer',
            padding: '4px 6px', color: 'inherit', display: 'flex', flexDirection: 'column',
            alignItems: 'flex-end', gap: 0,
          }}>
          <Sans s={9} c={C.dim} upper tracked>{t.balance}</Sans>
          <div>
            <Display s={20} w={500} c={C.ember}>
              {furyFmt(profile.balance)} <span style={{ fontSize: 12, color: C.dim }}>{t.rub}</span>
            </Display>
            <span style={{
              marginLeft: 8, color: C.ember, border: `1px solid ${C.ember}`, padding: '0 6px',
              fontFamily: F.sans, fontSize: 12, fontWeight: 700, lineHeight: '18px',
              display: 'inline-block', verticalAlign: 'middle',
            }}>+</span>
          </div>
        </button>

        {/* Cart */}
        <button onClick={() => setCartOpen(true)} style={{
          background: 'transparent', border: `1px solid ${C.line}`, cursor: 'pointer',
          padding: '10px 16px', color: C.hi, display: 'flex', alignItems: 'center', gap: 10,
          fontFamily: F.sans, fontSize: 11, letterSpacing: '0.18em', textTransform: 'uppercase',
        }}>
          <span>{t.nav_cart}</span>
          <span style={{
            background: C.blood, color: '#fff', padding: '1px 7px',
            borderRadius: 10, fontSize: 10, fontWeight: 700,
          }}>{cartCount}</span>
        </button>
      </header>
    );
  }

  // ── Hero / deal banner ───────────────────────────────────────────────────
  // Хук-таймер: возвращает {d, h, m} до полуночи. Тикает раз в секунду.
  function useCountdownToMidnight() {
    const [now, setNow] = React.useState(Date.now());
    React.useEffect(() => {
      const id = setInterval(() => setNow(Date.now()), 1000);
      return () => clearInterval(id);
    }, []);
    const midnight = new Date();
    midnight.setHours(24, 0, 0, 0);
    const diff = Math.max(0, midnight.getTime() - now);
    const totalSec = Math.floor(diff / 1000);
    const d = Math.floor(totalSec / 86400);
    const h = Math.floor((totalSec % 86400) / 3600);
    const m = Math.floor((totalSec % 3600) / 60);
    const pad2 = (n) => String(n).padStart(2, '0');
    return { d: pad2(d), h: pad2(h), m: pad2(m) };
  }

  function Hero({ shop }) {
    const siteSettings = useSiteSettings() || {};
    const vp = useViewport();
    const { t, add, servers, lang, cart } = shop;
    const cd = useCountdownToMidnight();
    // Сделка дня приезжает из бэкенда (fury-api.js → /api/daily-deal).
    const deal = window.FURY_DAILY_DEAL || null;
    const dealName = deal && deal.product
      ? (lang === 'ru' ? deal.product.name_ru : deal.product.name_en).toUpperCase()
      : t.deal_title;
    const dealDescParts = deal && deal.product && Array.isArray(deal.product.gear)
      ? deal.product.gear.slice(0, 4).join(' · ')
      : t.deal_desc;
    const dealPct = deal ? deal.discount_pct : 25;
    const dealAddId = deal ? deal.product_id : 'kit-survivor';
    // Находим картинку товара дня в полном списке (с image_url).
    const dealProduct = window.FURY_PRODUCTS && window.FURY_PRODUCTS.find((p) => p.id === dealAddId);
    const dealImage = dealProduct && dealProduct.image_url;
    return (
      <section style={{
        position: 'relative', height: vp.isMobile ? 'auto' : 320, minHeight: vp.isMobile ? 360 : 320, overflow: 'hidden',
        background: 'linear-gradient(135deg, #1a1410 0%, #2a1818 60%, #0d0a08 100%)',
      }}>
        {/* atmospheric layer — distant pine silhouette */}
        <svg viewBox="0 0 1440 320" preserveAspectRatio="none" style={{
          position: 'absolute', inset: 0, width: '100%', height: '100%', opacity: 0.55,
        }}>
          <defs>
            <linearGradient id="fog" x1="0" y1="0" x2="0" y2="1">
              <stop offset="0%" stopColor="#3a2018" stopOpacity="0" />
              <stop offset="100%" stopColor="#0d0a08" stopOpacity="1" />
            </linearGradient>
          </defs>
          <rect width="1440" height="320" fill="url(#fog)" />
          {Array.from({ length: 22 }).map((_, i) => {
            const x = i * 70 + (i % 3) * 12;
            const w = 32 + (i % 5) * 6;
            const h = 70 + (i % 4) * 25;
            return (
              <polygon key={i}
                points={`${x},${320} ${x + w / 2},${320 - h} ${x + w},${320}`}
                fill="#0a0806" />
            );
          })}
          {Array.from({ length: 16 }).map((_, i) => {
            const x = i * 95 + 40;
            const w = 26 + (i % 3) * 4;
            const h = 50 + (i % 4) * 20;
            return (
              <polygon key={'b' + i}
                points={`${x},${320} ${x + w / 2},${320 - h} ${x + w},${320}`}
                fill="#1a1410" opacity="0.7" />
            );
          })}
        </svg>
        {/* grain */}
        <div style={{
          position: 'absolute', inset: 0, opacity: 0.12, mixBlendMode: 'overlay',
          backgroundImage:
            "url(\"data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='160' height='160'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='2'/></filter><rect width='100%' height='100%' filter='url(%23n)'/></svg>\")",
        }} />
        {/* content: vertical on mobile, horizontal on desktop */}
        <div style={{
          position: 'relative', height: '100%',
          padding: vp.isMobile ? '12px 14px' : '16px 48px',
          display: 'flex',
          flexDirection: vp.isMobile ? 'column' : 'row',
          alignItems: vp.isMobile ? 'stretch' : 'stretch',
          gap: vp.isMobile ? 14 : 32,
          overflow: 'auto',
        }}>
          {/* Combined deal block: image | (title + controls) */}
          <div style={{
            flex: '1 1 auto', minWidth: 0, minHeight: 0,
            display: 'flex', flexDirection: 'row', alignItems: 'center', gap: 24,
          }}>
            {/* Image — leftmost */}
            <div style={{
              flex: vp.isMobile ? '0 0 auto' : '0 0 240px', alignSelf: 'stretch', height: vp.isMobile ? 180 : 'auto',
              display: 'flex', alignItems: 'center', justifyContent: 'center',
            }}>
              <div style={{
                width: '100%', height: '85%', position: 'relative',
                background: dealImage ? '#0d0a08' : 'transparent',
                border: `1px solid ${C.lineSoft}`, overflow: 'hidden',
                boxShadow: dealImage ? '0 10px 28px rgba(0,0,0,0.5)' : 'none',
              }}>
                {dealImage ? (
                  <img src={dealImage} alt="" style={{
                    position: 'absolute', inset: 0, width: '100%', height: '100%',
                    objectFit: 'contain', display: 'block',
                  }} />
                ) : (
                  <div style={{
                    position: 'absolute', inset: 0, display: 'flex', alignItems: 'center', justifyContent: 'center',
                    color: 'rgba(232,220,203,0.4)',
                  }}>
                    <FuryGlyph cat={dealProduct ? dealProduct.cat : 'kits'} size={80} />
                  </div>
                )}
              </div>
            </div>

            {/* Right side: title above controls */}
            <div style={{
              flex: '1 1 auto', minWidth: 0,
              display: 'flex', flexDirection: 'column', justifyContent: 'center', gap: 16,
            }}>
              {/* Title + description */}
              <div style={{ flexShrink: 0 }}>
                <Mono c={C.ember}>· {t.daily_deal.toUpperCase()}</Mono>
                <div style={{ marginTop: 4 }}>
                  <Display s={42} italic c={C.hi} w={400} style={{ lineHeight: 1.05 }}>{dealName}</Display>
                </div>
                {dealDescParts && (
                  <div style={{ marginTop: 10, maxWidth: 440 }}>
                    <Sans s={13} c={C.text} style={{ lineHeight: 1.45 }}>{dealDescParts}</Sans>
                  </div>
                )}
              </div>

              {/* Controls: CTA · −%% · countdown */}
              <div style={{ display: 'flex', alignItems: 'center', gap: 16, flexWrap: 'wrap', flexShrink: 0 }}>
                {(() => {
                  const inCartAlready = cart[dealAddId] && cart[dealAddId] > 0;
                  const dealRedeemed = !!(deal && deal.redeemed_today);
                  const dealUsedUp = inCartAlready || dealRedeemed;
                  return (
                    <Btn kind={dealUsedUp ? "ghost" : "blood"} disabled={dealUsedUp} onClick={() => { if (!dealUsedUp) add(dealAddId); }}>{t.deal_cta}</Btn>
                  );
                })()}
                <div style={{ display: 'flex', alignItems: 'baseline', gap: 4 }}>
                  <Display s={28} c={C.blood} w={600}>−{dealPct}%</Display>
                  <Sans s={10} c={C.dim} upper tracked>· {t.save}</Sans>
                </div>
                <div style={{ width: 1, height: 26, background: C.line }} />
                <div style={{ display: 'flex', gap: 12 }}>
                  {[[cd.d, t.days_short], [cd.h, t.hours_short], [cd.m, t.mins_short]].map(([v, l], i) => (
                    <div key={i} style={{ textAlign: 'center' }}>
                      <Display s={18} c={C.hi} w={500} style={{ lineHeight: 1 }}>{v}</Display>
                      <div style={{ marginTop: 2 }}><Mono c={C.dim} style={{ fontSize: 8 }}>{l}</Mono></div>
                    </div>
                  ))}
                </div>
              </div>
            </div>
          </div>

          {/* Social icons strip — column on desktop, row on mobile */}
          <div style={{
            flex: '0 0 auto', display: 'flex',
            flexDirection: vp.isMobile ? 'row' : 'column',
            gap: 8, alignSelf: 'flex-start',
            paddingTop: vp.isMobile ? 0 : 22,
          }}>
            <FurySocialIcon kind="discord"  url={siteSettings.discord_url} />
            <FurySocialIcon kind="vk"       url={siteSettings.vk_url} />
            <FurySocialIcon kind="tg"       url={siteSettings.tg_url} />
            <FurySocialIcon kind="youtube"  url={siteSettings.youtube_url} />
            <FurySocialIcon kind="steam"    url={siteSettings.mod_url} />
          </div>

          {/* Right: all servers stacked */}
          <div style={{
            flex: vp.isMobile ? '1 1 auto' : '0 0 340px', maxHeight: vp.isMobile ? 'none' : '100%',
            display: 'flex', flexDirection: 'column', gap: 6, overflow: 'auto',
            paddingRight: 4,
          }}>
            <Mono c={C.ember} style={{ marginBottom: 2 }}>· {t.server_status.toUpperCase()}</Mono>
            {servers.map((s) => {
              const map = lang === 'ru' ? s.map_ru : s.map_en;
              const full = s.online >= s.max;
              return (
                <div key={s.id} style={{
                  padding: '8px 12px', background: 'rgba(13,10,8,0.65)',
                  border: `1px solid ${C.lineSoft}`, display: 'flex', alignItems: 'center', gap: 12,
                }}>
                  <div style={{ flex: 1, minWidth: 0 }}>
                    <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
                      <span style={{
                        width: 9, height: 9, borderRadius: '50%',
                        background: s.live ? '#4caf50' : '#d04040',
                        boxShadow: s.live ? '0 0 6px rgba(76,175,80,0.75)' : '0 0 6px rgba(208,64,64,0.55)',
                        flexShrink: 0,
                      }} />
                      <Sans s={11} c={C.hi} style={{ fontWeight: 600 }}>{s.name}</Sans>
                      <Sans s={9} c={C.dim}>{map}</Sans>
                    </div>
                    <div style={{ marginTop: 2, display: 'flex', alignItems: 'center', gap: 10 }}>
                      <Mono c={full ? C.blood : C.dim} style={{ fontSize: 9 }}>
                        {s.online}/{s.max}
                      </Mono>
                      {s.ping != null && (
                        <Mono c={C.dim} style={{ fontSize: 9 }}>{s.ping} ms</Mono>
                      )}
                    </div>
                  </div>
                  <button
                    onClick={() => {
                      // Placeholder: если у сервера есть game_host:port — открываем steam://connect.
                      if (s.connect) { location.href = s.connect; return; }
                      alert(lang === 'ru' ? 'Адрес сервера ещё не настроен.' : 'Server address not configured yet.');
                    }}
                    style={{
                      background: C.blood, color: '#fff', cursor: 'pointer',
                      border: `1px solid ${C.blood}`,
                      padding: '6px 12px', fontFamily: F.sans, fontSize: 10,
                      letterSpacing: '0.14em', textTransform: 'uppercase', fontWeight: 600,
                      flexShrink: 0,
                    }}>
                    {lang === 'ru' ? 'Подключиться' : 'Connect'}
                  </button>
                </div>
              );
            })}
          </div>
        </div>
      </section>
    );
  }

  // ── Filter rail (horizontal pills above grid) ────────────────────────────
  function FilterRail({ shop }) {
    const { t, cat, setCat, sort, setSort, lang } = shop;
    const vp = useViewport();
    // Категории берём из API (window.FURY_CATEGORIES). Если ещё не загружены —
    // фоллбек на статический список из data.js (для первого рендера).
    const dynamic = (window.FURY_CATEGORIES || []);
    const cats = dynamic.length > 0
      ? [['all', t.cats.all], ...dynamic.map((c) => [c.id, lang === 'ru' ? c.name_ru : c.name_en])]
      : [
          ['all', t.cats.all], ['kits', t.cats.kits], ['build', t.cats.build],
          ['vehicles', t.cats.vehicles], ['vip', t.cats.vip], ['cosmetics', t.cats.cosmetics],
          ['currency', t.cats.currency], ['slots', t.cats.slots],
        ];
    return (
      <div style={{
        padding: vp.isMobile ? '14px 14px 8px' : '24px 48px 12px',
        display: 'flex', alignItems: 'center', gap: 6,
        borderBottom: `1px solid ${C.lineSoft}`,
        flexWrap: vp.isMobile ? 'nowrap' : 'wrap',
        overflowX: vp.isMobile ? 'auto' : 'visible',
      }}>
        {cats.map(([k, label]) => {
          const active = cat === k;
          return (
            <button key={k} onClick={() => setCat(k)} style={{
              background: active ? C.hi : 'transparent',
              color: active ? C.bg : C.text,
              flexShrink: 0,
              border: `1px solid ${active ? C.hi : C.line}`,
              padding: '8px 16px', cursor: 'pointer',
              fontFamily: F.sans, fontSize: 11, letterSpacing: '0.14em',
              textTransform: 'uppercase', fontWeight: 500,
              borderRadius: 999,
            }}>{label}</button>
          );
        })}
        <div style={{ flex: 1 }} />
        <Sans s={10} c={C.dim} upper tracked style={{ marginRight: 6 }}>{t.sort_by} —</Sans>
        <select value={sort} onChange={(e) => setSort(e.target.value)} style={{
          background: 'transparent', color: C.hi, border: `1px solid ${C.line}`,
          padding: '8px 12px', fontFamily: F.sans, fontSize: 11, letterSpacing: '0.1em',
          textTransform: 'uppercase', borderRadius: 999, cursor: 'pointer',
        }}>
          <option value="popular"    style={{ background: C.bg }}>{t.sort_popular}</option>
          <option value="price_asc"  style={{ background: C.bg }}>{t.sort_price_asc}</option>
          <option value="price_desc" style={{ background: C.bg }}>{t.sort_price_desc}</option>
          <option value="name_asc"   style={{ background: C.bg }}>{lang === 'ru' ? 'По названию (А→Я)' : 'Name (A→Z)'}</option>
          <option value="name_desc"  style={{ background: C.bg }}>{lang === 'ru' ? 'По названию (Я→А)' : 'Name (Z→A)'}</option>
          <option value="new"        style={{ background: C.bg }}>{t.sort_new}</option>
        </select>
      </div>
    );
  }

  // ── Cart drawer ──────────────────────────────────────────────────────────
  function CartDrawer({ shop }) {
    const vp = useViewport();
    const { t, cartOpen, setCartOpen, cartItems, subtotal, discount, total, setQty, remove, profile, lang,
            servers, serverId, setServerId } = shop;
    const [pending, setPending] = React.useState(false);
    const [err, setErr] = React.useState(null);
    const [done, setDone] = React.useState(null);
    const [giftTo, setGiftTo] = React.useState({}); // productId -> recipient SteamID64
    const activeServer = servers.find((s) => s.id === serverId) || servers[0];
    const STEAMID_RE = /^7656119\d{10}$/;

    async function onCheckout() {
      setErr(null); setDone(null);
      if (!profile || profile.loggedIn === false) {
        if (window.FURY_API) window.FURY_API.login();
        return;
      }
      if (!window.FURY_API) {
        setErr(lang === 'ru' ? 'Сервер недоступен' : 'Server unavailable');
        return;
      }
      // Подарочные позиции: проверяем SteamID64 получателя на клиенте.
      const enriched = [];
      for (const ci of cartItems) {
        const raw = (giftTo[ci.p.id] || '').trim();
        if (raw && !STEAMID_RE.test(raw)) {
          setErr(lang === 'ru'
            ? `Неверный SteamID64 для «${window.FURY_pname(ci.p)}» (нужно 17 цифр, начинается с 7656119)`
            : `Invalid SteamID64 for "${window.FURY_pname(ci.p)}"`);
          return;
        }
        enriched.push({ p: ci.p, id: ci.p.id, qty: ci.qty, recipient_steam_id: raw || null });
      }
      try {
        setPending(true);
        const res = await window.FURY_API.checkout(enriched, { useBonus: true, serverId });
        cartItems.forEach(({ p }) => remove(p.id));
        setGiftTo({});
        setDone(res);
      } catch (e) {
        if (e.status === 402) {
          setErr(lang === 'ru' ? 'Недостаточно средств — пополните баланс' : 'Insufficient funds — top up your balance');
        } else if (e.status === 401) {
          window.FURY_API.login();
        } else {
          setErr(String(e.message || e));
        }
      } finally {
        setPending(false);
      }
    }

    if (!cartOpen) return null;
    return (
      <>
        <div onClick={() => setCartOpen(false)} style={{
          position: 'absolute', inset: 0, background: 'rgba(0,0,0,0.7)', zIndex: 50,
          backdropFilter: 'blur(2px)',
        }} />
        <div style={{
          position: 'absolute', top: 0, right: 0, bottom: 0,
          width: vp.isMobile ? '100%' : 440,
          background: C.panel, zIndex: 51, display: 'flex', flexDirection: 'column',
          borderLeft: `1px solid ${C.line}`,
        }}>
          <div style={{ padding: '28px 32px', borderBottom: `1px solid ${C.lineSoft}` }}>
            <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline' }}>
              <Mono c={C.ember}>· {t.cart_title.toUpperCase()}</Mono>
              <button onClick={() => setCartOpen(false)} style={{
                background: 'transparent', border: 'none', color: C.dim, cursor: 'pointer',
                fontFamily: F.serif, fontSize: 22, lineHeight: 1,
              }}>✕</button>
            </div>
            <div style={{ marginTop: 8 }}>
              <Display s={32} italic>{cartItems.length} {t.cart_title.toLowerCase()}</Display>
            </div>
          </div>
          {cartItems.length === 0 ? (
            <div style={{ flex: 1, display: 'flex', alignItems: 'center', justifyContent: 'center', flexDirection: 'column', gap: 18 }}>
              <Display s={20} italic c={C.dim}>{t.cart_empty}</Display>
              <Btn kind="ghost" onClick={() => setCartOpen(false)}>{t.continue_shopping}</Btn>
            </div>
          ) : (
            <>
              <div style={{ flex: 1, overflow: 'auto', padding: '20px 32px' }}>
                {cartItems.map(({ p, qty }, i) => (
                  <div key={p.id} style={{
                    padding: '16px 0',
                    borderBottom: i < cartItems.length - 1 ? `1px solid ${C.lineSoft}` : 'none',
                  }}>
                    <div style={{ display: 'flex', gap: 14 }}>
                    <div style={{
                      width: 72, height: 72, background: p.image_url ? '#0d0a08' : furyTone(p.tone),
                      display: 'flex', alignItems: 'center', justifyContent: 'center',
                      color: 'rgba(232,220,203,0.75)', flexShrink: 0,
                      position: 'relative', overflow: 'hidden',
                    }}>
                      {p.image_url ? (
                        <img src={p.image_url} alt="" style={{
                          position: 'absolute', inset: 0, width: '100%', height: '100%',
                          objectFit: 'contain', display: 'block',
                        }} />
                      ) : (
                        <FuryGlyph cat={p.cat} size={42} />
                      )}
                    </div>
                    <div style={{ flex: 1, minWidth: 0 }}>
                      <Mono c={C.ember}>· {(window.FURY_CATEGORIES || []).find((c) => c.id === p.cat)
                        ? ((window.FURY_CATEGORIES || []).find((c) => c.id === p.cat)[lang === 'ru' ? 'name_ru' : 'name_en'])
                        : (t.cats[p.cat] || p.cat)}</Mono>
                      <div><Display s={16} italic w={500}>{window.FURY_pname(p)}</Display></div>
                      <div style={{ marginTop: 8, display: 'flex', alignItems: 'center', gap: 12 }}>
                        <div style={{ display: 'flex', alignItems: 'center', border: `1px solid ${C.line}` }}>
                          <button onClick={() => setQty(p.id, qty - 1)} style={{ background: 'transparent', border: 'none', color: C.hi, cursor: 'pointer', width: 24, height: 22, fontFamily: F.serif, fontSize: 14 }}>−</button>
                          <span style={{ width: 22, textAlign: 'center', fontFamily: F.serif, fontSize: 13, color: C.hi }}>{qty}</span>
                          {p.cat !== 'slots' && (
                          <button onClick={() => setQty(p.id, qty + 1)} style={{ background: 'transparent', border: 'none', color: C.hi, cursor: 'pointer', width: 24, height: 22, fontFamily: F.serif, fontSize: 14 }}>+</button>
                          )}
                        </div>
                        <button onClick={() => remove(p.id)} style={{ background: 'transparent', border: 'none', color: C.dim, cursor: 'pointer', fontFamily: F.sans, fontSize: 10, letterSpacing: '0.14em', textTransform: 'uppercase' }}>{t.remove}</button>
                      </div>
                    </div>
                    <div style={{ textAlign: 'right' }}>
                      <Display s={18} c={C.hi} w={500}>{furyFmt(window.furyLineTotal(p, qty))}</Display>
                      <div><Sans s={10} c={C.dim}>{t.rub}</Sans></div>
                    </div>
                    </div>
                    {p.cat === 'slots' && (
                      <div style={{ marginTop: 12, padding: '10px 12px', border: `1px solid ${C.line}`, background: C.bg }}>
                        <Sans s={10} c={C.dim} upper tracked>
                          {lang === 'ru' ? '🎁 Подарить другу (SteamID64) — необязательно' : '🎁 Gift to a friend (SteamID64) — optional'}
                        </Sans>
                        <input
                          type="text"
                          value={giftTo[p.id] || ''}
                          onChange={(e) => setGiftTo((g) => ({ ...g, [p.id]: e.target.value }))}
                          placeholder="7656119XXXXXXXXXX"
                          style={{
                            marginTop: 6, width: '100%', background: 'transparent',
                            border: `1px solid ${C.line}`, color: C.hi, padding: '8px 10px',
                            fontFamily: F.sans, fontSize: 12, boxSizing: 'border-box',
                          }} />
                        {(giftTo[p.id] || '').trim() && (
                          <div style={{ marginTop: 6 }}>
                            <Sans s={9} c={C.ember}>
                              {lang === 'ru'
                                ? 'Сервер выберет получатель при принятии. Откажется — деньги вернутся вам.'
                                : 'Recipient picks the server on accept. If declined, you get refunded.'}
                            </Sans>
                          </div>
                        )}
                      </div>
                    )}
                  </div>
                ))}
              </div>
              <div style={{ padding: '20px 32px 28px', borderTop: `1px solid ${C.lineSoft}` }}>
                <CartRow label={t.cart_subtotal} val={`${furyFmt(subtotal)} ${t.rub}`} />
                <CartRow label={`${t.cart_discount} · ${window.FURY_PROFILE.bonus}`} val={`−${furyFmt(discount)} ${t.rub}`} dim />
                <div style={{ height: 1, background: C.lineSoft, margin: '12px 0' }} />
                <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline' }}>
                  <Display s={16} italic>{t.cart_total}</Display>
                  <Display s={32} c={C.ember} w={500}>{furyFmt(total)} <span style={{ fontSize: 14, color: C.dim }}>{t.rub}</span></Display>
                </div>

                {/* server picker — общий для всей корзины */}
                <div style={{ marginTop: 14, padding: '10px 12px', border: `1px solid ${C.line}`, background: C.bg }}>
                  <Sans s={10} c={C.dim} upper tracked>
                    {lang === 'ru' ? 'Сервер доставки' : 'Delivery server'}
                  </Sans>
                  <select
                    value={serverId || ''}
                    onChange={(e) => setServerId(e.target.value)}
                    style={{
                      marginTop: 4, width: '100%', background: 'transparent',
                      border: `1px solid ${C.line}`, color: C.hi, padding: '8px 10px',
                      fontFamily: F.sans, fontSize: 12, cursor: 'pointer',
                    }}>
                    {servers.map((s) => (
                      <option key={s.id} value={s.id} style={{ background: C.bg }}>
                        {s.name} · {lang === 'ru' ? s.map_ru : s.map_en}
                      </option>
                    ))}
                  </select>
                </div>

                <div style={{ height: 14 }} />
                {err && (
                  <div style={{ marginBottom: 10, padding: '8px 12px', background: 'rgba(168,40,40,0.15)', border: `1px solid ${C.blood}` }}>
                    <Sans s={11} c={C.bloodHi}>{err}</Sans>
                  </div>
                )}
                {done && (
                  <div style={{ marginBottom: 10, padding: '8px 12px', background: 'rgba(40,80,40,0.15)', border: `1px solid ${C.ember}` }}>
                    <Sans s={11} c={C.ember}>
                      {lang === 'ru' ? 'Покупка оформлена · списано' : 'Purchase completed · charged'} {furyFmt(done.total_paid)} {t.rub}
                    </Sans>
                  </div>
                )}
                <Btn kind="blood" full disabled={pending} onClick={onCheckout}>
                  {pending ? (lang === 'ru' ? 'Обработка…' : 'Processing…') : (t.checkout + ' →')}
                </Btn>
              </div>
            </>
          )}
        </div>
      </>
    );
  }
  function CartRow({ label, val, dim }) {
    return (
      <div style={{ display: 'flex', justifyContent: 'space-between', padding: '3px 0' }}>
        <Sans s={11} c={dim ? C.dim : C.text} upper tracked>{label}</Sans>
        <Sans s={11} c={dim ? C.ember : C.hi}>{val}</Sans>
      </div>
    );
  }

  // ── Login screen (Steam OpenID) ──────────────────────────────────────────
  function LoginScreen({ lang }) {
    const ru = lang === 'ru';
    return (
      <div style={{
        height: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center',
        background: 'linear-gradient(135deg, #1a1410 0%, #0d0a08 60%, #2a1818 100%)',
      }}>
        <div style={{
          maxWidth: 440, padding: '40px 44px', textAlign: 'center',
          background: 'rgba(13,10,8,0.55)', border: `1px solid ${C.line}`, backdropFilter: 'blur(6px)',
        }}>
          <Mono c={C.ember}>· {ru ? 'АВТОРИЗАЦИЯ' : 'AUTHORISATION'}</Mono>
          <div style={{ marginTop: 12 }}>
            <Display s={36} italic w={500}>{ru ? 'Войдите через Steam' : 'Sign in with Steam'}</Display>
          </div>
          <div style={{ marginTop: 14 }}>
            <Sans s={13} c={C.text} style={{ lineHeight: 1.6 }}>
              {ru
                ? 'Профиль игрока, баланс и история покупок привязаны к вашему Steam ID. Авторизация безопасна — пароль не передаётся серверу FURY.'
                : 'Your profile, balance and order history are tied to your Steam ID. Authentication is safe — your password never reaches the FURY server.'}
            </Sans>
          </div>
          <div style={{ marginTop: 28 }}>
            <Btn kind="blood" full onClick={() => window.FURY_API && window.FURY_API.login()}>
              {ru ? 'Войти через Steam →' : 'Continue with Steam →'}
            </Btn>
          </div>
          <div style={{ marginTop: 18 }}>
            <Sans s={10} c={C.dim} upper tracked>
              {ru ? 'Steam OpenID · сессия 30 дней' : 'Steam OpenID · 30-day session'}
            </Sans>
          </div>
        </div>
      </div>
    );
  }

  // ── Profile ──────────────────────────────────────────────────────────────
  function ProfileScreen({ shop }) {
    const { t, profile, lang } = shop;
    const [inventoryItem, setInventoryItem] = React.useState(null);
    const [invSrvIdx, setInvSrvIdx] = React.useState(0);
    if (profile && profile.loggedIn === false) return <LoginScreen lang={lang} />;
    const joined = lang === 'ru' ? profile.joined_ru : profile.joined_en;
    const vipUntil = lang === 'ru' ? profile.vip_until_ru : profile.vip_until_en;
    const invServers = shop.servers || [];
    const invCur = invServers.length ? invServers[((invSrvIdx % invServers.length) + invServers.length) % invServers.length] : null;
    const invList = (profile.inventory || []).filter((it) => {
      const sv = it.server_id || '';
      if (sv === '') return true;                              // глобальный бакет — виден на всех
      return invCur ? sv === invCur.id : true;
    });
    return (
      <div style={{ overflow: 'auto', height: '100%' }}>
        {/* portrait header */}
        <section style={{
          position: 'relative', padding: '40px 48px', overflow: 'hidden',
          background: 'linear-gradient(135deg, #1a1410 0%, #2a1818 100%)',
          borderBottom: `1px solid ${C.line}`,
        }}>
          <div style={{ display: 'flex', alignItems: 'center', gap: 36 }}>
            <div style={{
              width: 140, height: 180, background: furyTone('crimson'),
              display: 'flex', alignItems: 'center', justifyContent: 'center',
              flexShrink: 0, position: 'relative', overflow: 'hidden',
            }}>
              {profile.avatar ? (
                <img
                  src={profile.avatar}
                  alt={profile.name || 'avatar'}
                  referrerPolicy="no-referrer"
                  onError={(e) => { e.currentTarget.style.display = 'none'; }}
                  style={{
                    position: 'absolute', inset: 0, width: '100%', height: '100%',
                    objectFit: 'cover', display: 'block',
                  }}
                />
              ) : (
                // Фоллбек, если у юзера в Steam нет публичной картинки —
                // показываем первую букву ника на тёмном фоне.
                <Display s={84} italic c={C.paper} w={400}>
                  {(profile.name || 'F').slice(0, 1).toUpperCase()}
                </Display>
              )}
              <div style={{
                position: 'absolute', inset: 0, pointerEvents: 'none',
                background: 'radial-gradient(ellipse at 50% 100%, rgba(0,0,0,0.6), transparent 60%)',
              }} />
            </div>
            <div style={{ flex: 1 }}>
              <Mono c={C.ember}>· {t.welcome.toUpperCase()}</Mono>
              <div style={{ marginTop: 8 }}>
                <Display s={54} italic w={400}>{profile.name}</Display>
              </div>
              <div style={{ marginTop: 18, display: 'flex', gap: 36 }}>
                <KV label={t.profile_steam} val={profile.steam} />
                <KV label={t.profile_joined} val={joined} />
                <KV label={t.profile_hours} val={furyFmt(profile.hours)} />
              </div>
            </div>
            <div style={{
              background: 'rgba(13,10,8,0.55)', padding: 22, minWidth: 260,
              border: `1px solid ${C.line}`,
            }}>
              <Mono c={C.ember}>· {lang === 'ru' ? 'ПРИОРИТЕТНЫЙ СЛОТ' : 'PRIORITY SLOT'}</Mono>
              <PrioritySlotList lang={lang} />
              <div style={{ marginTop: 18, paddingTop: 14, borderTop: `1px solid ${C.lineSoft}` }}>
                <button
                  onClick={async () => {
                    if (!window.FURY_API) return;
                    await window.FURY_API.logout();
                  }}
                  style={{
                    background: 'transparent', border: `1px solid ${C.line}`, cursor: 'pointer',
                    padding: '8px 14px', width: '100%', color: C.hi,
                    fontFamily: F.sans, fontSize: 10, letterSpacing: '0.18em',
                    textTransform: 'uppercase', fontWeight: 600,
                  }}
                  title={lang === 'ru' ? 'Выйти из Steam-аккаунта' : 'Sign out from Steam'}>
                  {lang === 'ru' ? '↪ Выйти из Steam' : '↪ Sign out'}
                </button>
              </div>
            </div>
          </div>
        </section>

        <section style={{ padding: '36px 48px' }}>
          {/* stats row — only balance now */}
          <div style={{ display: 'grid', gridTemplateColumns: '1fr', gap: 24, maxWidth: 360 }}>
            <Stat label={t.balance} val={furyFmt(profile.balance)} suffix={t.rub} />
          </div>

          {/* bonus code redeem */}
          <BonusCodeRedeem lang={lang} />

          {/* incoming gifts — принять/отклонить */}
          <IncomingGifts lang={lang} />


          {/* inventory full-width + tabbed history below */}
          <div style={{ marginTop: 36 }}>
            <div>
              <div style={{ display: 'flex', alignItems: 'center', gap: 14 }}>
                <Display s={20} italic w={500}>{t.profile_inventory}</Display>
                {invServers.length > 0 && (
                  <select
                    value={invCur ? invCur.id : ''}
                    onChange={(e) => { const i = invServers.findIndex((s) => s.id === e.target.value); if (i >= 0) setInvSrvIdx(i); }}
                    title={lang === 'ru' ? 'Сервер' : 'Server'}
                    style={{
                      background: 'transparent', border: `1px solid ${C.line}`, color: C.ember,
                      padding: '5px 10px', fontFamily: F.sans, fontSize: 11,
                      letterSpacing: '0.12em', textTransform: 'uppercase', cursor: 'pointer',
                    }}>
                    {invServers.map((s) => (
                      <option key={s.id} value={s.id} style={{ background: C.bg, color: C.hi, textTransform: 'none', letterSpacing: 'normal' }}>{s.name}</option>
                    ))}
                  </select>
                )}
                <div style={{ flex: 1, height: 1, background: C.line }} />
              </div>
              {invList.length === 0 && (
                <div style={{ marginTop: 16 }}><Sans s={12} c={C.dim}>{lang === 'ru' ? 'На этом сервере предметов нет' : 'No items for this server'}</Sans></div>
              )}
              <div style={{ marginTop: 16, display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: 12 }}>
                {invList.map((it, i) => {
                  const p = window.FURY_PRODUCTS.find((x) => x.id === it.id);
                  if (!p) return null;
                  const hasImage = !!(it.image_url || p.image_url);
                  const imgUrl = it.image_url || p.image_url;
                  return (
                    <div key={i}
                      onClick={() => setInventoryItem({ ...it, p })}
                      style={{
                        background: C.panel2, position: 'relative', cursor: 'pointer',
                        transition: 'all .15s',
                      }}
                      onMouseEnter={(e) => { e.currentTarget.style.outline = `1px solid ${C.ember}`; }}
                      onMouseLeave={(e) => { e.currentTarget.style.outline = 'none'; }}>
                      <div style={{
                        height: 110, background: hasImage ? '#0d0a08' : furyTone(p.tone),
                        display: 'flex', alignItems: 'center', justifyContent: 'center',
                        color: 'rgba(232,220,203,0.7)', position: 'relative', overflow: 'hidden',
                      }}>
                        {hasImage ? (
                          <img src={imgUrl} alt="" style={{
                            position: 'absolute', inset: 0, width: '100%', height: '100%',
                            objectFit: 'contain', display: 'block',
                          }} />
                        ) : (
                          <FuryGlyph cat={p.cat} size={50} />
                        )}
                      </div>
                      <div style={{ padding: 10 }}>
                        <Sans s={11} c={C.hi}>{window.FURY_pname(p)}</Sans>
                      </div>
                      <div style={{ position: 'absolute', top: 6, right: 6, background: C.blood, color: '#fff', fontFamily: F.sans, fontSize: 10, padding: '1px 6px', fontWeight: 700 }}>×{it.qty}</div>
                    </div>
                  );
                })}
              </div>
            </div>
            <div style={{ marginTop: 28 }}>
              <HistoryTabs lang={lang} />
            </div>
          </div>
        </section>
        {inventoryItem && <InventoryItemModal item={inventoryItem} onClose={() => setInventoryItem(null)} shop={shop} />}
      </div>
    );
  }

  // ── Profile: priority slot per-server list with status dots ──────────
  function PrioritySlotList({ lang }) {
    const [servers, setServers] = React.useState(null);
    const ru = lang === 'ru';
    React.useEffect(() => {
      let alive = true;
      const load = () => {
        fetch('/api/profile/priority-status', { credentials: 'include' })
          .then((r) => r.json())
          .then((d) => { if (alive && d && d.servers) setServers(d.servers); })
          .catch(() => {});
      };
      load();
      window.addEventListener('fury:priority-refresh', load);
      return () => { alive = false; window.removeEventListener('fury:priority-refresh', load); };
    }, []);
    if (!servers) {
      return <div style={{ marginTop: 12 }}><Sans s={11} c={C.dim}>{ru ? 'Загрузка…' : 'Loading…'}</Sans></div>;
    }
    if (servers.length === 0) {
      return <div style={{ marginTop: 12 }}><Sans s={11} c={C.dim}>{ru ? 'Серверов нет' : 'No servers'}</Sans></div>;
    }
    return (
      <div style={{ marginTop: 12, display: 'flex', flexDirection: 'column', gap: 8 }}>
        {servers.map((s) => {
          const dateStr = s.active && s.expires_at
            ? new Date(s.expires_at).toLocaleDateString(ru ? 'ru-RU' : 'en-US', { day: '2-digit', month: 'short', year: '2-digit' })
            : null;
          return (
            <div key={s.server_id} style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
              <span style={{
                width: 10, height: 10, borderRadius: '50%',
                background: s.active ? '#4caf50' : '#d04040',
                boxShadow: s.active ? '0 0 6px rgba(76,175,80,0.7)' : '0 0 6px rgba(208,64,64,0.5)',
                flexShrink: 0,
              }} />
              <div style={{ flex: 1, minWidth: 0 }}>
                <Sans s={11} c={C.hi}>{s.server_name}</Sans>
                {dateStr && <div><Sans s={9} c={C.dim}>{ru ? 'до' : 'till'} {dateStr}</Sans></div>}
              </div>
              <Sans s={9} c={s.active ? '#4caf50' : C.dim} upper tracked>
                {s.active ? (ru ? 'актив' : 'active') : (ru ? 'нет' : 'off')}
              </Sans>
            </div>
          );
        })}
      </div>
    );
  }

  // ── Profile: incoming gifts (accept / decline) ───────────────────────
  function IncomingGifts({ lang }) {
    const ru = lang === 'ru';
    const [data, setData] = React.useState(null);   // { items, servers }
    const [pick, setPick] = React.useState({});     // giftId -> serverId
    const [busy, setBusy] = React.useState(0);
    const [msg, setMsg] = React.useState(null);

    const load = React.useCallback(() => {
      if (!window.FURY_API || !window.FURY_API.giftsIncoming) { setData({ items: [], servers: [] }); return; }
      window.FURY_API.giftsIncoming()
        .then((d) => setData(d && d.items ? d : { items: [], servers: [] }))
        .catch(() => setData({ items: [], servers: [] }));
    }, []);
    React.useEffect(() => { load(); }, [load]);

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

    async function act(g, kind) {
      setMsg(null); setBusy(g.id);
      try {
        if (kind === 'accept') {
          let sv = null;
          if (g.is_slot) {
            sv = pick[g.id] || (data.servers[0] && data.servers[0].id) || null;
            if (!sv) { setMsg(ru ? 'Выберите сервер' : 'Choose a server'); setBusy(0); return; }
          }
          await window.FURY_API.giftAccept(g.id, sv);
          setMsg(ru ? 'Подарок принят' : 'Gift accepted');
        } else {
          await window.FURY_API.giftDecline(g.id);
          setMsg(ru ? 'Отклонено — деньги возвращены отправителю' : 'Declined — sender refunded');
        }
        window.dispatchEvent(new Event('fury:priority-refresh'));
        load();
      } catch (e) {
        const code = (e && e.data && e.data.error) || (e && e.message);
        const m = {
          slot_already_active: ru ? 'У вас уже активен слот на этом сервере — выберите другой или откажитесь' : 'Slot already active on this server',
          server_required: ru ? 'Выберите сервер' : 'Choose a server',
          gift_not_pending: ru ? 'Подарок уже обработан' : 'Gift already processed',
          not_your_gift: ru ? 'Это не ваш подарок' : 'Not your gift',
        };
        setMsg(m[code] || (ru ? 'Ошибка' : 'Error'));
      } finally {
        setBusy(0);
      }
    }

    return (
      <div style={{ marginTop: 24 }}>
        <SectionHead label={ru ? 'Входящие подарки' : 'Incoming gifts'} />
        <div style={{ marginTop: 14, display: 'flex', flexDirection: 'column', gap: 12 }}>
          {data.items.map((g) => {
            const name = ru ? g.product_name_ru : g.product_name_en;
            return (
              <div key={g.id} style={{
                border: `1px solid ${C.line}`, background: C.panel2, padding: '14px 16px',
                display: 'flex', flexWrap: 'wrap', alignItems: 'center', gap: 12,
              }}>
                <div style={{ flex: 1, minWidth: 180 }}>
                  <Sans s={13} c={C.hi}>{name}{g.qty > 1 ? ` ×${g.qty}` : ''}</Sans>
                  <div><Sans s={9} c={C.dim}>{ru ? 'от' : 'from'} {g.from_steam_id}</Sans></div>
                </div>
                {g.is_slot && (
                  <select
                    value={pick[g.id] || (data.servers[0] && data.servers[0].id) || ''}
                    onChange={(e) => setPick((s) => ({ ...s, [g.id]: e.target.value }))}
                    style={{
                      background: 'transparent', border: `1px solid ${C.line}`, color: C.hi,
                      padding: '8px 10px', fontFamily: F.sans, fontSize: 12, cursor: 'pointer',
                    }}>
                    {data.servers.map((s) => (
                      <option key={s.id} value={s.id} style={{ background: C.bg }}>
                        {s.name} · {ru ? s.map_ru : s.map_en}
                      </option>
                    ))}
                  </select>
                )}
                <button
                  disabled={busy === g.id}
                  onClick={() => act(g, 'accept')}
                  style={{
                    background: C.ember, border: 'none', color: C.bg, cursor: 'pointer',
                    padding: '8px 16px', fontFamily: F.sans, fontSize: 10, letterSpacing: '0.16em',
                    textTransform: 'uppercase', fontWeight: 700, opacity: busy === g.id ? 0.5 : 1,
                  }}>
                  {ru ? 'Принять' : 'Accept'}
                </button>
                <button
                  disabled={busy === g.id}
                  onClick={() => act(g, 'decline')}
                  style={{
                    background: 'transparent', border: `1px solid ${C.line}`, color: C.dim, cursor: 'pointer',
                    padding: '8px 16px', fontFamily: F.sans, fontSize: 10, letterSpacing: '0.16em',
                    textTransform: 'uppercase', fontWeight: 600, opacity: busy === g.id ? 0.5 : 1,
                  }}>
                  {ru ? 'Отклонить' : 'Decline'}
                </button>
              </div>
            );
          })}
        </div>
        {msg && (
          <div style={{ marginTop: 10 }}>
            <Sans s={11} c={C.ember}>{msg}</Sans>
          </div>
        )}
      </div>
    );
  }

  // ── Profile: tabbed paginated history (Orders / Top-ups / Gifts / Inventory) ─
  function HistoryTabs({ lang }) {
    const ru = lang === 'ru';
    const [tab, setTab] = React.useState('purchases');
    const [page, setPage] = React.useState(1);
    const [data, setData] = React.useState(null);
    const [busy, setBusy] = React.useState(false);
    const ENDPOINTS = {
      purchases: '/api/profile/purchases',
      topups:    '/api/profile/topups',
      gifts:     '/api/profile/gifts',
      inventory: '/api/profile/inventory-history',
    };
    React.useEffect(() => {
      let alive = true;
      setBusy(true);
      fetch(ENDPOINTS[tab] + '?page=' + page, { credentials: 'include' })
        .then((r) => r.json())
        .then((d) => { if (alive) { setData(d); setBusy(false); } })
        .catch(() => { if (alive) setBusy(false); });
      return () => { alive = false; };
    }, [tab, page]);
    React.useEffect(() => { setPage(1); }, [tab]);

    const TABS = [
      ['purchases', ru ? 'Заказы' : 'Orders'],
      ['topups',    ru ? 'Пополнения' : 'Top-ups'],
      ['gifts',     ru ? 'Подарки' : 'Gifts'],
      ['inventory', ru ? 'Инвентарь' : 'Inventory'],
    ];
    function fmtDate(ts) {
      const d = new Date(ts);
      const opts = { day: '2-digit', month: 'short', hour: '2-digit', minute: '2-digit' };
      return d.toLocaleString(ru ? 'ru-RU' : 'en-US', opts);
    }

    return (
      <div>
        <SectionHead label={ru ? 'История' : 'History'} />
        <div style={{ display: 'flex', gap: 4, marginTop: 14, borderBottom: `1px solid ${C.lineSoft}`, flexWrap: 'wrap' }}>
          {TABS.map(([k, label]) => {
            const active = tab === k;
            return (
              <button key={k} onClick={() => setTab(k)} style={{
                background: 'transparent', border: 'none', cursor: 'pointer',
                padding: '10px 14px', position: 'relative',
                fontFamily: F.sans, fontSize: 11, letterSpacing: '0.16em',
                textTransform: 'uppercase', fontWeight: 500,
                color: active ? C.hi : C.dim,
              }}>{label}
                {active && <span style={{ position: 'absolute', left: 0, right: 0, bottom: -1, height: 2, background: C.blood }} />}
              </button>
            );
          })}
        </div>

        <div style={{ marginTop: 16, minHeight: 200 }}>
          {busy && <Sans s={11} c={C.dim}>{ru ? 'Загрузка…' : 'Loading…'}</Sans>}
          {!busy && data && data.items && data.items.length === 0 && (
            <Sans s={11} c={C.dim}>{ru ? 'Пусто' : 'Empty'}</Sans>
          )}
          {!busy && data && data.items && data.items.length > 0 && (
            <div>
              {data.items.map((row, i) => (
                <div key={i} style={{
                  display: 'flex', alignItems: 'center', gap: 14, padding: '12px 0',
                  borderBottom: i < data.items.length - 1 ? `1px solid ${C.lineSoft}` : 'none',
                  flexWrap: 'wrap',
                }}>
                  <Sans s={11} c={C.dim} style={{ width: 130 }}>{fmtDate(row.created_at)}</Sans>
                  {tab === 'purchases' && (
                    <>
                      <Sans s={12} c={C.text} style={{ flex: 1, minWidth: 120 }}>{ru ? row.product_name_ru : row.product_name_en} <span style={{ color: C.dim }}>×{row.qty}</span></Sans>
                      <Display s={14} c={C.hi}>{furyFmt(row.total)} <span style={{ color: C.dim, fontSize: 10, fontFamily: F.sans }}>{ru ? 'руб' : 'rub'}</span></Display>
                    </>
                  )}
                  {tab === 'topups' && (
                    <>
                      <Sans s={11} c={C.text} upper tracked style={{ minWidth: 80 }}>{row.provider}</Sans>
                      <Sans s={12} c={C.text} style={{ flex: 1, minWidth: 100 }}>{ru ? 'Зачислено' : 'Credited'}: <span style={{ color: C.hi }}>+{furyFmt(row.amount)}</span></Sans>
                      <Sans s={11} c={C.dim}>{ru ? 'Итого на балансе' : 'Balance after'}: <span style={{ color: C.hi }}>{furyFmt(row.balance_after)}</span></Sans>
                    </>
                  )}
                  {tab === 'gifts' && (
                    <>
                      <Sans s={11} c={row.direction === 'out' ? C.blood : C.ember} upper tracked style={{ minWidth: 70 }}>
                        {row.direction === 'out' ? (ru ? 'Отдал' : 'Sent') : (ru ? 'Получил' : 'Got')}
                      </Sans>
                      <Sans s={12} c={C.text} style={{ flex: 1, minWidth: 120 }}>{ru ? row.product_name_ru : row.product_name_en} <span style={{ color: C.dim }}>×{row.qty}</span></Sans>
                      <Sans s={10} c={C.dim} style={{ fontFamily: F.mono }}>{row.counterparty}</Sans>
                    </>
                  )}
                  {tab === 'inventory' && (
                    <>
                      <Sans s={11} c={row.event === 'gift_out' ? C.blood : C.ember} upper tracked style={{ minWidth: 70 }}>
                        {row.event === 'purchase' ? (ru ? 'Куплен' : 'Bought') :
                         row.event === 'gift_in'  ? (ru ? 'Получен' : 'Received') :
                         row.event === 'gift_out' ? (ru ? 'Отдан' : 'Gifted') : row.event}
                      </Sans>
                      <Sans s={12} c={C.text} style={{ flex: 1, minWidth: 120 }}>{ru ? row.product_name_ru : row.product_name_en}</Sans>
                      <Sans s={12} c={row.event === 'gift_out' ? C.blood : C.hi}>{row.event === 'gift_out' ? '−' : '+'}{row.qty}</Sans>
                    </>
                  )}
                </div>
              ))}
            </div>
          )}
        </div>

        {!busy && data && data.pages > 1 && (
          <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 8, marginTop: 16 }}>
            <button onClick={() => setPage(Math.max(1, page - 1))} disabled={page <= 1} style={{
              background: 'transparent', border: `1px solid ${C.line}`, color: C.hi, padding: '6px 12px',
              cursor: page <= 1 ? 'not-allowed' : 'pointer', opacity: page <= 1 ? 0.4 : 1,
              fontFamily: F.sans, fontSize: 11,
            }}>←</button>
            <Sans s={11} c={C.dim}>{page} / {data.pages}</Sans>
            <button onClick={() => setPage(Math.min(data.pages, page + 1))} disabled={page >= data.pages} style={{
              background: 'transparent', border: `1px solid ${C.line}`, color: C.hi, padding: '6px 12px',
              cursor: page >= data.pages ? 'not-allowed' : 'pointer', opacity: page >= data.pages ? 0.4 : 1,
              fontFamily: F.sans, fontSize: 11,
            }}>→</button>
          </div>
        )}
      </div>
    );
  }

  // ── Inventory item modal (server transfer + gift) ─────────────────────
  function InventoryItemModal({ item, onClose, shop }) {
    const { servers, lang, t } = shop;
    const p = item.p;
    const ru = lang === 'ru';
    const [mode, setMode] = React.useState(null); // null | 'transfer' | 'gift'
    const [newServer, setNewServer] = React.useState(servers && servers[0] ? servers[0].id : '');
    const [giftId, setGiftId] = React.useState('');
    const [transferQty, setTransferQty] = React.useState(1);
    const [giftQty, setGiftQty] = React.useState(1);
    const [busy, setBusy] = React.useState(false);
    const [msg, setMsg] = React.useState(null);
    const [err, setErr] = React.useState(null);
    const pendingTotal = item.qty || 0;
    const imgUrl = item.image_url || p.image_url;
    // server_id -> человекочитаемое имя сервера (как в списке серверов).
    const srvName = (sid) => {
      const s = (servers || []).find((x) => x.id === sid);
      return s ? s.name : (sid || '—');
    };

    async function doCopyCmd() {
      setBusy(true); setErr(null); setMsg(null);
      try {
        const r = await fetch('/api/inventory/redeem-code', {
          method: 'POST', credentials: 'include',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ product_id: p.id, server_id: item.server_id || '' }),
        });
        const d = await r.json();
        if (!r.ok) {
          const em = {
            not_in_inventory: ru ? 'Этого предмета нет в инвентаре' : 'Not in inventory',
            not_claimable: ru ? 'Этот предмет нельзя получить командой в игре' : 'This item cannot be claimed in-game',
          };
          throw new Error(em[d.error] || d.error || ('http_' + r.status));
        }
        const command = d.command || ('$code ' + d.code);
        let ok = false;
        try { await navigator.clipboard.writeText(command); ok = true; } catch (_) {}
        if (!ok) {
          try {
            const ta = document.createElement('textarea');
            ta.value = command; ta.style.position = 'fixed'; ta.style.opacity = '0';
            document.body.appendChild(ta); ta.select();
            ok = document.execCommand('copy'); document.body.removeChild(ta);
          } catch (_) {}
        }
        setMsg((ok ? (ru ? 'Скопировано: ' : 'Copied: ') : (ru ? 'Команда: ' : 'Command: ')) + command + (ru ? ' — вставьте в игровой чат на сервере, предмет упадёт под ноги' : ' — paste into in-game chat on the server'));
      } catch (e) { setErr(e.message); } finally { setBusy(false); }
    }

    async function doTransfer() {
      setBusy(true); setErr(null); setMsg(null);
      try {
        const r = await fetch('/api/inventory/transfer-server', {
          method: 'POST', credentials: 'include',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ product_id: p.id, from_server_id: item.server_id || '', new_server_id: newServer, qty: transferQty }),
        });
        const d = await r.json();
        if (!r.ok) throw new Error(d.error || 'http_' + r.status);
        setMsg(ru ? `Перенесено ${d.moved} шт. на ${srvName(newServer)}` : `Moved ${d.moved} units to ${srvName(newServer)}`);
        if (d.profile) { window.FURY_PROFILE = d.profile; window.dispatchEvent(new CustomEvent('fury:data')); }
      } catch (e) { setErr(e.message); } finally { setBusy(false); }
    }
    async function doGift() {
      setBusy(true); setErr(null); setMsg(null);
      try {
        const r = await fetch('/api/inventory/gift', {
          method: 'POST', credentials: 'include',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ product_id: p.id, from_server_id: item.server_id || '', recipient_steam_id: giftId.trim(), qty: giftQty }),
        });
        const d = await r.json();
        if (!r.ok) throw new Error(d.error || 'http_' + r.status);
        setMsg(ru ? `Отправлено ${d.gifted} шт. — ждёт подтверждения получателем (вернётся вам при отказе/через 24 ч)` : `Sent ${d.gifted} — awaiting recipient's confirmation (returns to you if declined / after 24h)`);
        if (d.profile) { window.FURY_PROFILE = d.profile; window.dispatchEvent(new CustomEvent('fury:data')); }
        setTimeout(onClose, 1200);
      } catch (e) { setErr(e.message); } finally { setBusy(false); }
    }

    return (
      <>
        <div onClick={onClose} style={{
          position: 'absolute', inset: 0, background: 'rgba(0,0,0,0.78)', zIndex: 60, backdropFilter: 'blur(4px)',
        }} />
        <div style={{
          position: 'absolute', top: '50%', left: '50%', transform: 'translate(-50%, -50%)',
          width: 'min(560px, 92vw)', maxHeight: '90vh', overflow: 'auto',
          background: C.panel, zIndex: 61, border: `1px solid ${C.line}`,
        }}>
          <div style={{ padding: '24px 28px', borderBottom: `1px solid ${C.lineSoft}`, display: 'flex', justifyContent: 'space-between', alignItems: 'baseline' }}>
            <Mono c={C.ember}>· {ru ? 'ПРЕДМЕТ В ИНВЕНТАРЕ' : 'INVENTORY ITEM'}</Mono>
            <button onClick={onClose} style={{ background: 'transparent', border: 'none', color: C.dim, cursor: 'pointer', fontFamily: F.serif, fontSize: 22 }}>✕</button>
          </div>
          <div style={{ padding: '24px 28px', display: 'flex', gap: 20 }}>
            <div style={{ width: 140, height: 140, flexShrink: 0, background: imgUrl ? '#0d0a08' : furyTone(p.tone), position: 'relative', overflow: 'hidden', border: `1px solid ${C.lineSoft}` }}>
              {imgUrl ? (
                <img src={imgUrl} alt="" style={{ position: 'absolute', inset: 0, width: '100%', height: '100%', objectFit: 'contain', display: 'block' }} />
              ) : (
                <div style={{ position: 'absolute', inset: 0, display: 'flex', alignItems: 'center', justifyContent: 'center', color: 'rgba(232,220,203,0.5)' }}><FuryGlyph cat={p.cat} size={64} /></div>
              )}
            </div>
            <div style={{ flex: 1, minWidth: 0 }}>
              <Display s={22} italic w={500}>{window.FURY_pname(p)}</Display>
              <div style={{ marginTop: 6 }}><Sans s={11} c={C.dim} upper tracked>{p.cat}</Sans></div>
              <div style={{ marginTop: 14, display: 'flex', gap: 28, flexWrap: 'wrap' }}>
                <div>
                  <Sans s={9} c={C.dim} upper tracked>{ru ? 'В инвентаре' : 'In inventory'}</Sans>
                  <div><Display s={20} c={C.hi}>×{item.qty}</Display></div>
                </div>
                <div>
                  <Sans s={9} c={C.dim} upper tracked>{ru ? 'Сервер выдачи' : 'Delivery server'}</Sans>
                  <div><Sans s={14} c={C.hi}>{item.server_id ? srvName(item.server_id) : (ru ? 'не задан (любой)' : 'unassigned (any)')}</Sans></div>
                </div>
              </div>
            </div>
          </div>

          {/* Action buttons */}
          <div style={{ padding: '0 28px 16px', display: 'flex', gap: 10, flexWrap: 'wrap' }}>
            {item.claimable && (
              <Btn kind="blood" disabled={busy} onClick={doCopyCmd}>
                {busy ? '…' : (ru ? '⧉ Скопировать команду' : '⧉ Copy command')}
              </Btn>
            )}
            <Btn kind={mode === 'transfer' ? 'blood' : 'ghost'} onClick={() => { setMode(mode === 'transfer' ? null : 'transfer'); setErr(null); setMsg(null); }}>
              {ru ? '↻ Переместить на сервер' : '↻ Move to server'}
            </Btn>
            <Btn kind={mode === 'gift' ? 'blood' : 'ghost'} onClick={() => { setMode(mode === 'gift' ? null : 'gift'); setErr(null); setMsg(null); }}>
              {ru ? '✿ Передать другу' : '✿ Gift to friend'}
            </Btn>
          </div>

          {mode === 'transfer' && (
            <div style={{ padding: '0 28px 24px' }}>
              <Sans s={10} c={C.dim} upper tracked>{ru ? 'Переносит выбранное кол-во этого предмета с его текущего сервера на другой.' : 'Moves the chosen amount of this item from its current server to another.'}</Sans>
              <div style={{ display: 'flex', gap: 10, marginTop: 12, flexWrap: 'wrap', alignItems: 'center' }}>
                <select value={newServer} onChange={(e) => setNewServer(e.target.value)} style={{ flex: '1 1 200px', background: 'transparent', color: C.hi, border: `1px solid ${C.line}`, padding: '10px 12px', fontFamily: F.sans, fontSize: 13 }}>
                  {servers.map((s) => (<option key={s.id} value={s.id} style={{ background: C.bg }}>{s.name} — {lang === 'ru' ? s.map_ru : s.map_en}</option>))}
                </select>
                <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
                  <Sans s={10} c={C.dim} upper tracked>{ru ? 'шт.' : 'qty'}</Sans>
                  <input type="number" min={1} max={Math.max(1, pendingTotal)} value={transferQty}
                    onChange={(e) => setTransferQty(Math.max(1, Math.min(pendingTotal || 1, Math.floor(Number(e.target.value) || 1))))}
                    style={{ width: 70, background: 'transparent', color: C.hi, border: `1px solid ${C.line}`, padding: '10px 8px', fontFamily: F.mono, fontSize: 13, textAlign: 'center' }} />
                  <Sans s={10} c={C.dim}>/ {pendingTotal}</Sans>
                </div>
                <Btn kind="blood" disabled={busy || pendingTotal === 0 || transferQty < 1} onClick={doTransfer}>{busy ? '…' : (ru ? 'Перенести' : 'Move')}</Btn>
              </div>
              {pendingTotal === 0 && <div style={{ marginTop: 10 }}><Sans s={11} c={C.blood}>{ru ? 'Нет запланированных доставок для перемещения.' : 'No pending deliveries to move.'}</Sans></div>}
            </div>
          )}

          {mode === 'gift' && (
            <div style={{ padding: '0 28px 24px' }}>
              <Sans s={10} c={C.dim} upper tracked>{ru ? 'Отправляет N шт. как подарок: предмет уйдёт из вашего инвентаря и будет ждать, пока получатель примет. Если откажется или не примет за 24 ч — вернётся вам. Steam ID (17 цифр, с 7656119).' : 'Sends N units as a pending gift: item leaves your inventory until the recipient accepts. Declined / not accepted in 24h → returns to you. Steam ID (17 digits, starts with 7656119).'}</Sans>
              <div style={{ display: 'flex', gap: 10, marginTop: 12, flexWrap: 'wrap', alignItems: 'center' }}>
                <input value={giftId} onChange={(e) => setGiftId(e.target.value)} placeholder="76561198..." style={{ flex: '1 1 220px', background: 'transparent', color: C.hi, border: `1px solid ${C.line}`, padding: '10px 12px', fontFamily: F.mono, fontSize: 13 }} />
                <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
                  <Sans s={10} c={C.dim} upper tracked>{ru ? 'шт.' : 'qty'}</Sans>
                  <input type="number" min={1} max={item.qty} value={giftQty}
                    onChange={(e) => setGiftQty(Math.max(1, Math.min(item.qty || 1, Math.floor(Number(e.target.value) || 1))))}
                    style={{ width: 70, background: 'transparent', color: C.hi, border: `1px solid ${C.line}`, padding: '10px 8px', fontFamily: F.mono, fontSize: 13, textAlign: 'center' }} />
                  <Sans s={10} c={C.dim}>/ {item.qty}</Sans>
                </div>
                <Btn kind="blood" disabled={busy || !/^7656119\d{10}$/.test(giftId.trim()) || giftQty < 1 || giftQty > item.qty} onClick={doGift}>{busy ? '…' : (ru ? 'Передать' : 'Gift')}</Btn>
              </div>
            </div>
          )}

          {(msg || err) && (
            <div style={{ padding: '0 28px 20px' }}>
              {msg && <Sans s={11} c={C.ember}>{msg}</Sans>}
              {err && <Sans s={11} c={C.blood}>{err}</Sans>}
            </div>
          )}
        </div>
      </>
    );
  }

  function KV({ label, val }) {
    return (
      <div>
        <Sans s={10} c={C.dim} upper tracked>{label}</Sans>
        <div style={{ marginTop: 2 }}><Sans s={13} c={C.hi}>{val}</Sans></div>
      </div>
    );
  }
  function Stat({ label, val, suffix, accent }) {
    return (
      <div style={{ borderLeft: `1px solid ${C.line}`, paddingLeft: 18 }}>
        <Sans s={10} c={C.dim} upper tracked>{label}</Sans>
        <div style={{ marginTop: 6 }}>
          <Display s={36} c={accent || C.hi} w={500}>{val}</Display>
          {suffix && <Sans s={12} c={C.dim} style={{ marginLeft: 4 }}>{suffix}</Sans>}
        </div>
      </div>
    );
  }
  function SectionHead({ label }) {
    return (
      <div style={{ display: 'flex', alignItems: 'center', gap: 14 }}>
        <Display s={20} italic w={500}>{label}</Display>
        <div style={{ flex: 1, height: 1, background: C.line }} />
      </div>
    );
  }

  // ── Servers ──────────────────────────────────────────────────────────────
  function ServersScreen({ shop }) {
    const { t, servers, lang, setView, setServerId, serverId } = shop;
    return (
      <div style={{ overflow: 'auto', height: '100%' }}>
        <section style={{
          padding: '48px 48px 32px',
          background: 'linear-gradient(180deg, #1a1410 0%, transparent 100%)',
        }}>
          <Mono c={C.ember}>· {t.nav_servers.toUpperCase()}</Mono>
          <div style={{ marginTop: 8 }}><Display s={48} italic w={400}>{t.server_pick}</Display></div>
        </section>
        <div style={{ padding: '0 48px 48px', display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 24 }}>
          {servers.map((s) => {
            const sel = s.id === serverId;
            const map = lang === 'ru' ? s.map_ru : s.map_en;
            return (
              <article key={s.id} style={{
                position: 'relative', background: C.panel, overflow: 'hidden',
                boxShadow: sel ? `inset 0 0 0 1.5px ${C.ember}, 0 0 0 1px ${C.ember}` : `inset 0 0 0 1px ${C.lineSoft}`,
                cursor: 'pointer',
              }} onClick={() => setServerId(s.id)}>
                {/* image strip */}
                <div style={{
                  height: 120, background: furyTone(s.id === 'fury-3' ? 'frost' : s.id === 'fury-4' ? 'frost' : 'olive'),
                  position: 'relative', overflow: 'hidden',
                }}>
                  <div style={{
                    position: 'absolute', inset: 0,
                    background: 'linear-gradient(180deg, transparent 0%, rgba(13,10,8,0.7) 100%)',
                  }} />
                  {s.full && (
                    <div style={{
                      position: 'absolute', top: 12, right: 12, background: C.blood,
                      color: '#fff', padding: '4px 12px', fontFamily: F.sans, fontSize: 10,
                      letterSpacing: '0.2em', textTransform: 'uppercase', fontWeight: 600,
                    }}>{t.server_full}</div>
                  )}
                  {sel && (
                    <div style={{
                      position: 'absolute', top: 12, left: 12, background: C.ember,
                      color: '#1a1208', padding: '4px 12px', fontFamily: F.sans, fontSize: 10,
                      letterSpacing: '0.2em', textTransform: 'uppercase', fontWeight: 600,
                    }}>● {t.online}</div>
                  )}
                  <div style={{
                    position: 'absolute', bottom: 14, left: 18, right: 18,
                  }}>
                    <Display s={26} italic c={C.paper} w={500}>{s.name}</Display>
                  </div>
                </div>
                <div style={{ padding: '20px 22px 22px' }}>
                  <Sans s={11} c={C.dim} upper tracked>{map} · {s.mods}</Sans>
                  <div style={{ marginTop: 14, display: 'grid', gridTemplateColumns: 'repeat(3,1fr)', gap: 14 }}>
                    <div><Sans s={9} c={C.dim} upper tracked>{t.online}</Sans><div><Display s={20} c={C.hi}>{s.online}<span style={{ color: C.dim, fontSize: 12 }}>/{s.max}</span></Display></div></div>
                    <div><Sans s={9} c={C.dim} upper tracked>{t.ping}</Sans><div><Display s={20} c={C.hi}>{s.ping}<span style={{ color: C.dim, fontSize: 10 }}>ms</span></Display></div></div>
                    <div><Sans s={9} c={C.dim} upper tracked>{t.map}</Sans><div><Display s={16} italic c={C.hi}>{map}</Display></div></div>
                  </div>
                  <div style={{ marginTop: 14, display: 'flex', alignItems: 'center', gap: 8 }}>
                    {s.tags.map((tag) => (
                      <span key={tag} style={{
                        border: `1px solid ${C.line}`, color: C.text, padding: '4px 10px',
                        fontFamily: F.sans, fontSize: 9, letterSpacing: '0.16em',
                        textTransform: 'uppercase', borderRadius: 999,
                      }}>{tag}</span>
                    ))}
                    <div style={{ flex: 1 }} />
                    <Btn kind={sel ? 'blood' : 'ghost'} disabled={s.full}
                      onClick={(e) => { e.stopPropagation(); setServerId(s.id); setView('shop'); }}
                      style={{ padding: '10px 18px', fontSize: 10 }}>
                      {s.full ? t.server_full : t.server_join} →
                    </Btn>
                  </div>
                </div>
              </article>
            );
          })}
        </div>
      </div>
    );
  }

  // ── Shop main ────────────────────────────────────────────────────────────
  function ShopScreen({ shop }) {
    const { t, products, add, setQty, cart, select, lang } = shop;
    const vp = useViewport();
    const profile = shop.profile || {};
    const isOwner = profile.role === 'owner';
    const [reorderMode, setReorderMode] = React.useState(false);
    const [dragId, setDragId] = React.useState(null);
    const [overId, setOverId] = React.useState(null);
    const [busy, setBusy] = React.useState(false);
    return (
      <div style={{ flex: 1, overflow: 'auto' }}>
        <Hero shop={shop} />
        <FilterRail shop={shop} />
        <div style={{ padding: vp.isMobile ? '20px 14px 32px' : '32px 48px 48px' }}>
          <div style={{ display: 'flex', alignItems: 'baseline', gap: 18, marginBottom: 22, flexWrap: 'wrap' }}>
            <Display s={28} italic w={400}>{t.cats[shop.cat]}</Display>
            <div style={{ flex: 1, height: 1, background: C.line, minWidth: 20 }} />
            {isOwner && (
              <button onClick={() => setReorderMode(!reorderMode)} title={shop.lang === 'ru' ? 'Перетаскивайте карточки в нужный порядок' : 'Drag cards to reorder'} style={{
                background: reorderMode ? C.blood : 'transparent',
                color: reorderMode ? '#fff' : C.text,
                border: `1px solid ${reorderMode ? C.blood : C.line}`,
                padding: '6px 12px', cursor: 'pointer', fontFamily: F.sans, fontSize: 10,
                letterSpacing: '0.14em', textTransform: 'uppercase', fontWeight: 600,
              }}>
                {reorderMode ? (shop.lang === 'ru' ? 'Готово' : 'Done') : (shop.lang === 'ru' ? '⇅ Порядок' : '⇅ Reorder')}
              </button>
            )}
            <Sans s={11} c={C.dim} upper tracked>{products.length} {shop.lang === 'ru' ? 'товаров' : 'items'}</Sans>
          </div>
          <div style={{ display: 'grid', gridTemplateColumns: vp.isMobile ? '1fr' : (vp.isTablet ? 'repeat(3, minmax(0,1fr))' : 'repeat(6, minmax(0, 1fr))'), gap: vp.isMobile ? 12 : 16 }}>
            {products.map((p, i) => {
              const isDragging = reorderMode && dragId === p.id;
              const isOver = reorderMode && overId === p.id && dragId && dragId !== p.id;
              return (
                <div key={p.id}
                  draggable={reorderMode}
                  onDragStart={(e) => {
                    if (!reorderMode) return;
                    setDragId(p.id);
                    // Critical: Firefox/Chrome require setData to start a drag.
                    e.dataTransfer.effectAllowed = 'move';
                    try { e.dataTransfer.setData('text/plain', p.id); } catch (_) {}
                  }}
                  onDragOver={(e) => {
                    if (!reorderMode || !dragId) return;
                    e.preventDefault();
                    e.dataTransfer.dropEffect = 'move';
                    if (overId !== p.id) setOverId(p.id);
                  }}
                  onDragLeave={() => { if (overId === p.id) setOverId(null); }}
                  onDrop={async (e) => {
                    if (!reorderMode || !dragId || dragId === p.id) return;
                    e.preventDefault();
                    const targetId = p.id;
                    const draggedId = dragId;
                    setOverId(null); setDragId(null);
                    const prev = window.FURY_PRODUCTS;
                    const newList = reorderProducts(draggedId, targetId);
                    if (!newList) return;
                    // Optimistic: apply locally first so user sees the swap NOW.
                    window.FURY_PRODUCTS = newList;
                    window.__furyDataVersion = (window.__furyDataVersion || 0) + 1;
                    window.dispatchEvent(new CustomEvent('fury:data'));
                    setBusy(true);
                    const ok = await saveReorder(newList);
                    setBusy(false);
                    if (!ok) {
                      // Revert if server refused.
                      window.FURY_PRODUCTS = prev;
                      window.__furyDataVersion = (window.__furyDataVersion || 0) + 1;
                      window.dispatchEvent(new CustomEvent('fury:data'));
                    }
                  }}
                  onDragEnd={() => { setDragId(null); setOverId(null); }}
                  style={{
                    position: 'relative',
                    opacity: isDragging ? 0.4 : 1,
                    outline: isOver ? `2px dashed ${C.blood}` : 'none',
                    outlineOffset: -2,
                    cursor: reorderMode ? (isDragging ? 'grabbing' : 'grab') : 'default',
                  }}>
                  <ProductCard index={i} lang={lang} p={p} t={t} inCart={cart[p.id]} onAdd={add} setQty={setQty} onOpen={reorderMode ? () => {} : select} />
                  {reorderMode && (
                    <>
                      {/* Click-blocking overlay so inner buttons do not steal the drag. */}
                      <div style={{
                        position: 'absolute', inset: 0, zIndex: 6,
                        background: 'rgba(13,10,8,0.05)', cursor: isDragging ? 'grabbing' : 'grab',
                      }} />
                      <div style={{
                        position: 'absolute', top: 8, left: 8, zIndex: 7,
                        background: 'rgba(13,10,8,0.85)', border: `1px solid ${C.line}`,
                        padding: '4px 8px', fontFamily: F.mono, fontSize: 10, color: C.text,
                        pointerEvents: 'none',
                      }}>⇅ {i + 1}</div>
                    </>
                  )}
                </div>
              );
            })}
          </div>
        </div>
        <SiteFooter lang={lang} />
      </div>
    );
  }

  // ── Detail modal ─────────────────────────────────────────────────────────
  function DetailModal({ shop }) {
    const { t, selected, closeSelected, add, setCartOpen, buyQty, setBuyQty, cart, lang,
            servers, serverId, setServerId } = shop;
    if (!selected) return null;
    const p = selected;
    const total = window.furyLineTotal(p, Math.max(1, buyQty));
    // Категория — динамическая
    const dynCats = window.FURY_CATEGORIES || [];
    const catRow = dynCats.find((x) => x.id === p.cat);
    const catLabel = catRow ? (lang === 'ru' ? catRow.name_ru : catRow.name_en) : (t.cats[p.cat] || p.cat);
    return (
      <>
        <div onClick={closeSelected} style={{
          position: 'absolute', inset: 0, background: 'rgba(0,0,0,0.78)', zIndex: 60, backdropFilter: 'blur(4px)',
        }} />
        <div style={{
          position: 'absolute', top: '50%', left: '50%', transform: 'translate(-50%,-50%)',
          width: 620, background: C.panel, zIndex: 61, color: C.text,
          border: `1px solid ${C.line}`, boxShadow: '0 40px 100px rgba(0,0,0,0.6)',
          display: 'flex', flexDirection: 'column', maxHeight: 800,
        }}>
          {/* edition strip */}
          <div style={{
            padding: '12px 28px', borderBottom: `1px solid ${C.lineSoft}`,
            display: 'flex', alignItems: 'center',
          }}>
            <Mono c={C.dim}>FURY · № {String(window.FURY_PRODUCTS.indexOf(p) + 1).padStart(3, '0')} · {catLabel.toUpperCase()}</Mono>
            <div style={{ flex: 1 }} />
            <button onClick={closeSelected} style={{
              background: 'transparent', border: 'none', color: C.dim, cursor: 'pointer',
              fontFamily: F.serif, fontSize: 24, lineHeight: 1, padding: 0,
            }}>✕</button>
          </div>
          {/* hero image */}
          <div style={{ position: 'relative' }}>
            <CardArt p={p} h={260} />
            <Badge kind={p.badge} t={t} />
          </div>
          {/* title overlapping */}
          <div style={{ padding: '24px 28px 0' }}>
            <div style={{
              marginTop: -54, marginBottom: 16,
              background: C.bg, padding: '14px 18px', display: 'inline-block',
              border: `1px solid ${C.line}`,
            }}>
              <Display s={32} italic w={500} c={C.hi}>{window.FURY_pname(p)}</Display>
            </div>
            {(() => {
              const desc = window.FURY_desc(p);
              return desc ? (
                <div style={{ color: C.text, fontFamily: F.sans, fontSize: 13, lineHeight: 1.6, whiteSpace: 'pre-wrap' }}>
                  {desc}
                </div>
              ) : null;
            })()}
            {(p.gear || []).length > 0 && (
              <div style={{ marginTop: 18 }}>
                <Mono c={C.ember}>· {lang === 'ru' ? 'СОДЕРЖИМОЕ' : 'CONTENTS'}</Mono>
                <div style={{ marginTop: 8, display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '4px 16px' }}>
                  {p.gear.map((g, i) => (
                    <div key={i} style={{ display: 'flex', gap: 8, alignItems: 'baseline', padding: '4px 0', borderBottom: `1px dotted ${C.lineSoft}` }}>
                      <Sans s={12} c={C.text} style={{ flex: 1 }}>{g}</Sans>
                      <Mono c={C.ember}>✓</Mono>
                    </div>
                  ))}
                </div>
              </div>
            )}
          </div>
          {/* footer */}
          <div style={{
            padding: '20px 28px 24px', marginTop: 'auto',
            borderTop: `1px solid ${C.lineSoft}`, background: C.bg,
          }}>
            {/* Сервер доставки. Выбор глобален — все следующие покупки уедут на этот сервер
                пока пользователь не сменит. Иконка автодоставки покажет server_id. */}
            <div style={{ marginBottom: 16 }}>
              <Sans s={10} c={C.dim} upper tracked>{lang === 'ru' ? 'Доставить на сервер' : 'Deliver to server'}</Sans>
              <select
                value={serverId || ''}
                onChange={(e) => setServerId(e.target.value)}
                style={{
                  marginTop: 4, width: '100%', background: 'transparent',
                  border: `1px solid ${C.line}`, color: C.hi, padding: '10px 12px',
                  fontFamily: F.sans, fontSize: 13, cursor: 'pointer',
                }}>
                {servers.map((s) => (
                  <option key={s.id} value={s.id} style={{ background: C.bg }}>
                    {s.name} · {lang === 'ru' ? s.map_ru : s.map_en} · {s.online}/{s.max}
                  </option>
                ))}
              </select>
            </div>

            <div style={{ display: 'flex', alignItems: 'center', gap: 18 }}>
              {p.cat !== 'slots' && (
              <div>
                <Sans s={10} c={C.dim} upper tracked>{t.quantity}</Sans>
                <div style={{ display: 'flex', alignItems: 'center', marginTop: 4, border: `1px solid ${C.line}` }}>
                  <button onClick={() => setBuyQty(Math.max(1, buyQty - 1))} style={{ background: 'transparent', border: 'none', color: C.hi, cursor: 'pointer', width: 30, height: 32, fontFamily: F.serif, fontSize: 16 }}>−</button>
                  <input value={buyQty} onChange={(e) => setBuyQty(Math.max(1, Number(e.target.value) || 1))}
                    style={{ width: 42, height: 32, background: 'transparent', border: 'none', outline: 'none', color: C.hi, textAlign: 'center', fontFamily: F.serif, fontSize: 16 }} />
                  <button onClick={() => setBuyQty(buyQty + 1)} style={{ background: 'transparent', border: 'none', color: C.hi, cursor: 'pointer', width: 30, height: 32, fontFamily: F.serif, fontSize: 16 }}>+</button>
                </div>
              </div>
              )}
              <div style={{ flex: 1 }} />
              <div style={{ textAlign: 'right' }}>
                <Sans s={10} c={C.dim} upper tracked>{t.cart_total}</Sans>
                <div><Display s={32} c={C.ember} w={500}>{furyFmt(total)} <span style={{ fontSize: 14, color: C.dim }}>{t.rub}</span></Display></div>
              </div>
            </div>
            <div style={{ display: 'flex', gap: 10, marginTop: 14 }}>
              <Btn kind="ghost" onClick={closeSelected} style={{ flex: 1 }}>{lang === 'ru' ? 'Закрыть' : 'Close'}</Btn>
              <Btn kind="blood" onClick={() => { const n = p.cat === 'slots' ? 1 : buyQty; for (let i = 0; i < n; i++) add(p.id); closeSelected(); setCartOpen(true); }} style={{ flex: 1.4 }}>
                {t.buy_now} →
              </Btn>
            </div>
          </div>
        </div>
      </>
    );
  }

  // ── Root ─────────────────────────────────────────────────────────────────
  // Глобальные CSS-анимации. Инжектим СИНХРОННО при загрузке модуля, чтобы
  // keyframe был в DOM ДО первого React-рендера (иначе animation указывает
  // на несуществующий keyframe и браузер тихо игнорирует анимацию).
  (function injectGlobalStyles() {
    if (typeof document === 'undefined') return;
    if (document.getElementById('fury-anim-styles')) return;
    const s = document.createElement('style');
    s.id = 'fury-anim-styles';
    s.textContent = `
      /* fade-up на entry анимирует только opacity (transform трогаем у inner-картинки).
         Hover-подъём делается через 'top' — он НЕ конфликтует с animation-эффектами,
         в отличие от transform у карточки, который заблокирован keyframe'ом. */
      @keyframes fury-fade-up {
        from { opacity: 0; }
        to   { opacity: 1; }
      }
      .fury-grid-item {
        opacity: 0;
        animation: fury-fade-up 520ms cubic-bezier(.2,.7,.2,1) both;
      }
      .fury-card {
        position: relative;
        top: 0;
        outline: 1px solid transparent;
        outline-offset: -1px;
        transition: top 220ms cubic-bezier(.2,.7,.2,1),
                    box-shadow 220ms cubic-bezier(.2,.7,.2,1),
                    outline-color 220ms ease;
        will-change: top;
      }
      .fury-card:hover {
        top: -8px;
        outline-color: rgba(168, 40, 40, 0.95);
        box-shadow: 0 22px 55px rgba(0,0,0,0.70),
                    0 0 0 1px rgba(168, 40, 40, 0.55),
                    0 0 28px rgba(168, 40, 40, 0.18);
      }
      .fury-card .fury-card-img {
        transition: transform 520ms cubic-bezier(.2,.7,.2,1),
                    filter 520ms ease;
        filter: brightness(0.92);
      }
      .fury-card:hover .fury-card-img {
        transform: scale(1.10);
        filter: brightness(1.04);
      }
      .fury-card .fury-buy-btn { transition: background 200ms ease, color 200ms ease; }
      .fury-card:hover .fury-buy-btn { background: rgba(168, 40, 40, 1); }
    `;
    document.head.appendChild(s);
  })();

  function CinematicApp({ fullscreen = false }) {
    const shop = useShop('shop');
    const rootStyle = fullscreen
      ? { width: '100%', height: '100vh', position: 'relative', overflow: 'hidden' }
      : { width: 1440, height: 900, position: 'relative', overflow: 'hidden' };
    return (
      <div data-screen-label="C · Cinematic" style={{
        ...rootStyle, background: C.bg, color: C.text,
        fontFamily: F.sans, display: 'flex', flexDirection: 'column',
      }}>
        <Header shop={shop} />
        <div style={{ flex: 1, display: 'flex', flexDirection: 'column', minHeight: 0 }}>
          {shop.view === 'shop' && <ShopScreen shop={shop} />}
          {shop.view === 'profile' && <ProfileScreen shop={shop} />}
          {shop.view === 'servers' && <ServersScreen shop={shop} />}
          {shop.view === 'admin' && <AdminScreen shop={shop} />}
          {shop.view === 'mail' && <MailScreen shop={shop} />}
        </div>
        <CartDrawer shop={shop} />
        <DetailModal shop={shop} />
      </div>
    );
  }

  window.CinematicApp = CinematicApp;
})();
