// nyashelf-screens.jsx
// All screen components: Dashboard, FigureDetail, Calendar, Search, Wishlist,
// Settings, plus Loading / Empty / Error states.

// ─────────────────────────────────────────────────────────────────────────────
// Reusable: FigureCard (compact 2-column)
// ─────────────────────────────────────────────────────────────────────────────
function FigureCard({ figure, onClick, density = 'comfy' }) {
  const fmt = useFormat();
  const f = figure;
  const compact = density === 'tight';
  const imgH = compact ? 130 : 150;
  const cardH = compact ? 240 : 268; // fixed so every card is identical regardless of text length
  return (
    <GlassCard padding={compact ? 8 : 10} onClick={onClick} style={{
      overflow: 'hidden',
      height: cardH,
      display: 'flex', flexDirection: 'column',
    }}>
      <FigureArt figure={f} height={imgH} />
      <div style={{
        padding: '10px 4px 2px',
        flex: 1, display: 'flex', flexDirection: 'column',
        minHeight: 0,
      }}>
        {/* meta row — fixed height so status pill always aligns */}
        <div style={{
          display: 'flex', alignItems: 'center', justifyContent: 'space-between',
          gap: 6, height: 22, flexShrink: 0,
        }}>
          <span style={{
            fontSize: 10.5, color: 'var(--text-dim)', fontWeight: 600,
            textTransform: 'lowercase', letterSpacing: '.04em',
            overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap',
            minWidth: 0, flexShrink: 1,
          }}>
            {f.maker} · {f.scale}
          </span>
          <span style={{ flexShrink: 0 }}>
            <StatusPill status={f.status} size="sm" />
          </span>
        </div>
        {/* name — 1 line, fixed height */}
        <div style={{
          marginTop: 5, fontFamily: 'Comfortaa, sans-serif',
          fontSize: 14, fontWeight: 600, color: 'var(--text)',
          lineHeight: '17px', height: 17,
          overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap',
          flexShrink: 0,
        }}>{f.name}</div>
        {/* line — 1 line, fixed height */}
        <div style={{
          fontSize: 11, color: 'var(--text-dim)', marginTop: 2,
          lineHeight: '14px', height: 14,
          overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap',
          flexShrink: 0,
        }}>
          {f.line}
        </div>
        {/* spacer pushes price row to the bottom */}
        <div style={{ flex: 1 }}/>
        {/* price row — pinned to bottom */}
        <div style={{
          display: 'flex', alignItems: 'baseline', justifyContent: 'space-between',
          gap: 6, height: 20, flexShrink: 0,
        }}>
          <span style={{
            fontFamily: 'Comfortaa, sans-serif', fontWeight: 600, fontSize: 14, color: 'var(--text)',
            overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap',
            minWidth: 0, flexShrink: 1,
          }}>
            {fmt(f.price)}
          </span>
          {f.payDays > 0 ? (
            <span style={{
              fontSize: 10.5, color: f.payDays <= 3 ? '#ffb3c9' : 'var(--text-dim)',
              fontWeight: 600, whiteSpace: 'nowrap', flexShrink: 0,
            }}>
              до оплаты {f.payDays} дн
            </span>
          ) : f.release ? (
            <span style={{
              fontSize: 10.5, color: 'var(--text-dim)', fontWeight: 500,
              whiteSpace: 'nowrap', flexShrink: 0,
            }}>
              {nyaFormatDate(f.release)}
            </span>
          ) : null}
        </div>
      </div>
    </GlassCard>
  );
}

// ─────────────────────────────────────────────────────────────────────────────
// 1. Dashboard
// ─────────────────────────────────────────────────────────────────────────────
function DashboardScreen({ go, tweaks }) {
  const fmt = useFormat();
  const toast = useToast();
  const [refreshing, setRefreshing] = React.useState(false);
  const figures = useFigures();
  const stats = React.useMemo(() => {
    const active = figures.filter(f => f.status !== 'delivered');
    const monthPay = figures
      .filter(f => f.status === 'preorder' && f.payDays <= 30 && f.payDays > 0)
      .reduce((s, f) => s + (f.price - f.paid), 0);
    const totalCommit = figures
      .filter(f => f.status === 'preorder' || f.status === 'soon')
      .reduce((s, f) => s + (f.price - f.paid), 0);
    return { count: active.length, monthPay, totalCommit };
  }, [figures]);

  const sorted = [...figures]
    .filter(f => f.status !== 'delivered')
    .sort((a, b) => {
      const aP = a.payDays || 9999, bP = b.payDays || 9999;
      return aP - bP;
    });

  const handleRefresh = async () => {
    setRefreshing(true);
    await new Promise(r => setTimeout(r, 1200));
    setRefreshing(false);
    toast.show('обновлено', { kind: 'success' });
  };

  return (
    <PullToRefresh onRefresh={handleRefresh}>
    <div style={{ padding: '0 16px 100px' }}>
      <DecorLayer density={tweaks.decor * 0.6} seed="dashboard" palette={tweaks.palette} />

      {/* Greeting */}
      <div style={{ position: 'relative', zIndex: 2, paddingTop: 6 }}>
        <div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
          <GhostKitten size={56} variant="happy" />
          <div style={{ flex: 1 }}>
            <div style={{ fontSize: 12, color: 'var(--text-dim)', fontWeight: 500, display: 'inline-flex', alignItems: 'center', gap: 5 }}>
              добрый вечер
              <Sparkle size={9} color="var(--accent-a)" opacity={1}/>
            </div>
            <h1 style={{
              margin: '2px 0 0', fontFamily: 'Comfortaa, sans-serif', fontWeight: 600,
              fontSize: 24, lineHeight: 1,
              background: 'linear-gradient(135deg, #fff 0%, var(--accent-a) 70%, var(--accent-b) 100%)',
              WebkitBackgroundClip: 'text', backgroundClip: 'text', color: 'transparent',
            }}>твоя полочка</h1>
          </div>
          <IconChip onClick={() => go('notifications')}>
            {I.bell(18)}
            <span style={{
              position: 'absolute', top: 6, right: 6, width: 8, height: 8,
              borderRadius: '50%', background: 'var(--accent-a)',
              boxShadow: '0 0 6px var(--glow-strong)',
            }}/>
          </IconChip>
        </div>
      </div>

      {/* Hero stat card */}
      <div style={{ marginTop: 18, position: 'relative', zIndex: 2 }}>
        <GlassCard padding={18} glow style={{ position: 'relative', overflow: 'hidden' }}>
          <div style={{
            position: 'absolute', top: -30, right: -30, width: 140, height: 140,
            borderRadius: '50%',
            background: 'radial-gradient(circle, var(--glow-soft) 0%, transparent 65%)',
            filter: 'blur(8px)', pointerEvents: 'none',
          }}/>
          <div style={{ fontSize: 12, color: 'var(--text-dim)', fontWeight: 500 }}>в этом месяце к оплате</div>
          <div style={{
            marginTop: 4, fontFamily: 'Comfortaa, sans-serif', fontWeight: 600,
            fontSize: 34, letterSpacing: '-.01em', lineHeight: 1,
            background: 'linear-gradient(135deg, #fff, var(--accent-a) 80%)',
            WebkitBackgroundClip: 'text', backgroundClip: 'text', color: 'transparent',
          }}>{fmt(stats.monthPay)}</div>
          <div style={{
            marginTop: 14, display: 'flex', gap: 10, alignItems: 'stretch',
            paddingTop: 14, borderTop: '1px dashed var(--card-border)',
          }}>
            <div style={{ flex: 1 }}>
              <div style={{ fontSize: 11, color: 'var(--text-dim)' }}>предзаказов</div>
              <div style={{ fontFamily: 'Comfortaa, sans-serif', fontWeight: 600, fontSize: 19, marginTop: 2 }}>
                {stats.count}
              </div>
            </div>
            <div style={{ width: 1, background: 'var(--card-border)' }}/>
            <div style={{ flex: 1.5 }}>
              <div style={{ fontSize: 11, color: 'var(--text-dim)' }}>всего к доплате</div>
              <div style={{ fontFamily: 'Comfortaa, sans-serif', fontWeight: 600, fontSize: 19, marginTop: 2 }}>
                {fmt(stats.totalCommit)}
              </div>
            </div>
          </div>
        </GlassCard>
      </div>

      {/* Quick actions row */}
      <div style={{ display: 'flex', gap: 8, marginTop: 14, position: 'relative', zIndex: 2 }}>
        <GradientButton size="sm" icon={I.plus(14)} onClick={() => go('search')} style={{ flex: 1 }}>
          добавить
        </GradientButton>
        <GhostButton size="sm" icon={I.calendar(14)} onClick={() => go('calendar')} style={{ flex: 1 }}>
          календарь
        </GhostButton>
      </div>

      {/* Upcoming payments callout */}
      <SectionLabel action={
        <button onClick={() => go('calendar')} style={{
          background: 'transparent', border: 0, color: 'var(--accent-a)',
          fontSize: 12, fontWeight: 600, cursor: 'pointer',
          display: 'inline-flex', alignItems: 'center', gap: 2,
        }}>смотреть {I.chevRight(12)}</button>
      }>скоро оплата</SectionLabel>

      {(() => {
        const urgent = sorted.filter(f => f.payDays > 0 && f.payDays <= 14).slice(0, 2);
        return (
          <div style={{ display: 'flex', flexDirection: 'column', gap: 8, position: 'relative', zIndex: 2 }}>
            {urgent.map(f => (
              <GlassCard key={f.id} padding={10} onClick={() => go('detail', f)} style={{ display: 'flex', gap: 12, alignItems: 'center' }}>
                <div style={{ width: 52, height: 52, flexShrink: 0 }}>
                  <FigureArt figure={f} height={52} style={{ borderRadius: 14 }} />
                </div>
                <div style={{ flex: 1, minWidth: 0 }}>
                  <div style={{ fontFamily: 'Comfortaa, sans-serif', fontWeight: 600, fontSize: 13.5,
                                overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
                    {f.name}
                  </div>
                  <div style={{ fontSize: 11, color: 'var(--text-dim)', marginTop: 2 }}>
                    {fmt(f.price - f.paid)} · {nyaFormatDate(f.release)}
                  </div>
                </div>
                <div style={{
                  padding: '6px 12px', borderRadius: 9999,
                  background: f.payDays <= 3
                    ? 'linear-gradient(135deg, #ff9ecd, #ffb3a8)'
                    : 'rgba(255,255,255,.08)',
                  border: f.payDays <= 3 ? 0 : '1px solid var(--card-border)',
                  color: f.payDays <= 3 ? '#3a1e4d' : 'var(--text)',
                  fontSize: 12, fontWeight: 600, whiteSpace: 'nowrap',
                  boxShadow: f.payDays <= 3 ? '0 0 14px var(--glow-soft)' : 'none',
                }}>
                  {f.payDays} {ruPlural(f.payDays, ['день', 'дня', 'дней'])}
                </div>
              </GlassCard>
            ))}
          </div>
        );
      })()}

      <SectionLabel>все заказы</SectionLabel>
      {refreshing ? (
        <div style={{
          display: 'grid', gridTemplateColumns: 'repeat(2, minmax(0, 1fr))', gap: 10,
          position: 'relative', zIndex: 2,
        }}>
          {Array.from({ length: 4 }).map((_, i) => <SkeletonCard key={i} />)}
        </div>
      ) : (
        <div style={{
          display: 'grid', gridTemplateColumns: 'repeat(2, minmax(0, 1fr))', gap: 10,
          position: 'relative', zIndex: 2,
        }}>
          {sorted.map(f => (
            <FigureCard key={f.id} figure={f} onClick={() => go('detail', f)} />
          ))}
        </div>
      )}
    </div>
    </PullToRefresh>
  );
}

// ─────────────────────────────────────────────────────────────────────────────
// 2. Figure detail
// ─────────────────────────────────────────────────────────────────────────────
// Floating chevron button on the hero image (used in detail screen).
const arrowStyle = (side) => ({
  position: 'absolute', top: '50%',
  [side]: 10,
  transform: 'translateY(-50%)',
  width: 36, height: 36, borderRadius: '50%', border: 0,
  background: 'rgba(20,12,40,.55)',
  backdropFilter: 'blur(10px)', WebkitBackdropFilter: 'blur(10px)',
  color: '#fff',
  display: 'flex', alignItems: 'center', justifyContent: 'center',
  cursor: 'pointer', padding: 0, zIndex: 5,
  boxShadow: '0 4px 12px rgba(0,0,0,.3)',
});

const zoomBtnStyle = {
  width: 32, height: 32, borderRadius: '50%', border: 0,
  background: 'rgba(20,12,40,.6)',
  backdropFilter: 'blur(10px)', WebkitBackdropFilter: 'blur(10px)',
  color: '#fff', cursor: 'pointer', padding: 0,
  fontSize: 16, fontWeight: 500,
  display: 'flex', alignItems: 'center', justifyContent: 'center',
  boxShadow: '0 2px 8px rgba(0,0,0,.3)',
};

const _EMPTY_FIGURE = {
  id: '_', name: '', scale: '', tone: '', maker: '', line: '',
  image_urls: [], image_url: null, price: 0, currency: 'EUR',
  status: '', release: null, payDays: null, paid: 0,
  wishlist: false, tags: [], hue: 0, char: '', category: '',
  product_url: null, order_number: null,
};

function FigureDetailScreen({ go, goBack, figureId, figureData, tweaks, wishlist, toggleWishlist, openCatalogWithTag }) {
  const fmt = useFormat();
  const toast = useToast();
  const figures = useFigures();
  // Cache the figure object so it survives a later refetch that may drop
  // the item (e.g. user removes it from wishlist while on this screen).
  const figureRef = React.useRef(figureData);
  if (figureData && figureData !== figureRef.current) {
    figureRef.current = figureData;
  }
  const fActual = figureRef.current || figures.find(x => x.id === figureId);
  // _EMPTY_FIGURE keeps every hook below safe when fActual is null;
  // we still render the "not found" view at the bottom in that case.
  const f = fActual || _EMPTY_FIGURE;
  // Use the wishlist Set as the single source of truth. The figure
  // object may carry a stale `wishlist: true` from when it was first
  // fetched — after a DELETE the Set is updated but the cached figure
  // isn't, so reading the Set keeps the heart in sync.
  const isWishProduct = (figure) => figure && figure.product_url && wishlist.has(figure.product_url);
  const isOrderItem = !!(f && f.order_number);  // ordered → wishlist heart makes no sense
  const handleShare = React.useCallback(() => {
    if (!f) return;
    const url = f.product_url;
    const tg = window.Telegram && window.Telegram.WebApp;
    if (tg && tg.openTelegramLink && url) {
      const share = 'https://t.me/share/url?url=' + encodeURIComponent(url)
        + '&text=' + encodeURIComponent(f.name || '');
      try { tg.openTelegramLink(share); return; } catch (_) {}
    }
    if (url && navigator.share) {
      navigator.share({ title: f.name, url }).catch(() => {});
      return;
    }
    if (url && navigator.clipboard) {
      navigator.clipboard.writeText(url).then(() => toast.show('ссылка скопирована', { kind: 'success' }));
    }
  }, [f, toast]);
  const [notesOpen, setNotesOpen] = React.useState(false);
  const isWish = isWishProduct(f);
  const progress = f.paid / f.price;
  // When the figure carries a real gallery, treat each photo as a view;
  // otherwise fall back to the mock's 4 stylized angles for the SVG art.
  const views = (f.image_urls && f.image_urls.length)
    ? Array.from({ length: Math.min(f.image_urls.length, 10) }, (_, i) => i)
    : ['front', 'close', 'back', 'box'];
  const viewLabels = { front: 'спереди', close: 'крупно', back: 'сбоку', box: 'коробка' };
  const [viewIdx, setViewIdx] = React.useState(0);
  const [zoom, setZoom] = React.useState(false);
  // lightbox zoom/pan state — wheel & double-click change scale, drag pans when scaled
  const [scale, setScale] = React.useState(1);
  const [pan, setPan] = React.useState({ x: 0, y: 0 });
  const view = views[viewIdx];
  const setView = (v) => setViewIdx(views.indexOf(v));
  const prevView = React.useCallback(() => { setScale(1); setPan({x:0,y:0}); setViewIdx(i => (i - 1 + views.length) % views.length); }, []);
  const nextView = React.useCallback(() => { setScale(1); setPan({x:0,y:0}); setViewIdx(i => (i + 1) % views.length); }, []);

  // Reset zoom/pan whenever the lightbox closes or the view changes
  React.useEffect(() => { setScale(1); setPan({ x: 0, y: 0 }); }, [zoom, viewIdx]);

  const onWheel = (e) => {
    e.preventDefault();
    setScale(s => Math.min(4, Math.max(1, s + (e.deltaY > 0 ? -0.2 : 0.2))));
  };
  const onDblClick = () => setScale(s => s > 1 ? 1 : 2);

  // pinch + drag handlers
  const pinchRef = React.useRef({ d0: 0, s0: 1 });
  const dragRef = React.useRef({ x: 0, y: 0, px: 0, py: 0, down: false });
  const onTouchStart = (e) => {
    if (e.touches.length === 2) {
      const dx = e.touches[0].clientX - e.touches[1].clientX;
      const dy = e.touches[0].clientY - e.touches[1].clientY;
      pinchRef.current = { d0: Math.hypot(dx, dy), s0: scale };
    } else if (e.touches.length === 1 && scale > 1) {
      dragRef.current = { x: e.touches[0].clientX, y: e.touches[0].clientY, px: pan.x, py: pan.y, down: true };
    } else {
      window.__nyaSwipe = e.touches[0].clientX;
    }
  };
  const onTouchMove = (e) => {
    if (e.touches.length === 2 && pinchRef.current.d0) {
      const dx = e.touches[0].clientX - e.touches[1].clientX;
      const dy = e.touches[0].clientY - e.touches[1].clientY;
      const d = Math.hypot(dx, dy);
      setScale(Math.min(4, Math.max(1, pinchRef.current.s0 * (d / pinchRef.current.d0))));
      e.preventDefault();
    } else if (dragRef.current.down && scale > 1) {
      setPan({
        x: dragRef.current.px + (e.touches[0].clientX - dragRef.current.x),
        y: dragRef.current.py + (e.touches[0].clientY - dragRef.current.y),
      });
      e.preventDefault();
    }
  };
  const onTouchEnd = (e) => {
    if (dragRef.current.down) { dragRef.current.down = false; return; }
    if (pinchRef.current.d0) { pinchRef.current.d0 = 0; return; }
    // single-touch swipe → switch view, but only if not zoomed in
    if (scale === 1) {
      const x0 = window.__nyaSwipe; if (x0 == null) return;
      const dx = e.changedTouches[0].clientX - x0;
      if (Math.abs(dx) > 40) (dx > 0 ? prevView : nextView)();
      window.__nyaSwipe = null;
    }
  };

  // mouse-drag pan when zoomed
  const onMouseDown = (e) => {
    if (scale <= 1) return;
    dragRef.current = { x: e.clientX, y: e.clientY, px: pan.x, py: pan.y, down: true };
    const move = (ev) => {
      if (!dragRef.current.down) return;
      setPan({
        x: dragRef.current.px + (ev.clientX - dragRef.current.x),
        y: dragRef.current.py + (ev.clientY - dragRef.current.y),
      });
    };
    const up = () => {
      dragRef.current.down = false;
      window.removeEventListener('mousemove', move);
      window.removeEventListener('mouseup', up);
    };
    window.addEventListener('mousemove', move);
    window.addEventListener('mouseup', up);
  };

  // Keyboard nav inside the lightbox: ← → to swap, Esc to close.
  React.useEffect(() => {
    if (!zoom) return;
    const onKey = (e) => {
      if (e.key === 'ArrowLeft') prevView();
      else if (e.key === 'ArrowRight') nextView();
      else if (e.key === 'Escape') setZoom(false);
    };
    window.addEventListener('keydown', onKey);
    return () => window.removeEventListener('keydown', onKey);
  }, [zoom, prevView, nextView]);

  // Auto-generated description — same template, figure-specific bits filled in.
  const description = React.useMemo(() => {
    const heightStr = f.scale === '1/6' ? '~28 см' : f.scale === '1/7' ? '~24 см' : '~21 см';
    const toneText = {
      lilac: 'нежно-сиреневая палитра и струящаяся ткань платья',
      pink:  'розовые тона и воздушный силуэт',
      blue:  'прохладная сине-голубая гамма и аккуратные складки',
      rose:  'румяные оттенки и романтичный образ',
      gold:  'золотистая палитра и сияющие детали',
    }[f.tone] || 'мягкие нежные тона и приятный силуэт';
    const lineStr = f.line ? `из линейки «${f.line}» ` : '';
    const scaleStr = f.scale ? `в масштабе ${f.scale} ` : '';
    const makerStr = f.maker ? `от ${f.maker}` : '';
    const headerLine = [f.name, ' ', lineStr.trim() ? '— ' + lineStr : '', scaleStr, makerStr].join('').trim().replace(/\s+/g, ' ');
    return `${headerLine}. ` +
           `${toneText[0].toUpperCase() + toneText.slice(1)}. Высота фигурки ${heightStr}, ` +
           `материал — ПВХ и АБС-пластик. Основание входит в комплект.`;
  }, [f]);

  // mock price history — 8 points. Robust to non-numeric ids (catalog
  // items use 'cat-238722' which parseInt would NaN out on).
  const priceHistory = React.useMemo(() => {
    const idStr = String(f.id || '');
    let s = 0;
    for (let i = 0; i < idStr.length; i++) s = (s * 31 + idStr.charCodeAt(i)) & 0x7fffffff;
    if (!s) s = 1;
    const rng = () => { s = (s*1103515245+12345) & 0x7fffffff; return s / 0x7fffffff; };
    const base = f.price || 100;
    return Array.from({ length: 8 }, (_, i) => ({
      m: ['авг','сен','окт','ноя','дек','янв','фев','мар'][i],
      p: Math.round(base * (0.85 + rng() * 0.25)),
    }));
  }, [f]);

  const pMin = Math.min(...priceHistory.map(p => p.p));
  const pMax = Math.max(...priceHistory.map(p => p.p));
  const w = 320, hCh = 90, pad = 6;
  const pts = priceHistory.map((p, i) => {
    const x = pad + (i / (priceHistory.length - 1)) * (w - pad * 2);
    const y = pad + (1 - (p.p - pMin) / (pMax - pMin || 1)) * (hCh - pad * 2);
    return { x, y, ...p };
  });
  const linePath = pts.map((p, i) => `${i === 0 ? 'M' : 'L'} ${p.x} ${p.y}`).join(' ');
  const areaPath = `${linePath} L ${pts[pts.length-1].x} ${hCh} L ${pts[0].x} ${hCh} Z`;

  // Figure went away while we're on this screen (e.g. user removed from
  // wishlist via the heart and refetch dropped it). Render a soft "back"
  // state instead of crashing.
  if (!fActual) {
    return (
      <div style={{ padding: 60, textAlign: 'center', color: 'var(--text-dim)' }}>
        <div style={{ fontSize: 14, marginBottom: 14 }}>фигурки больше нет в списке</div>
        <button onClick={goBack} style={{
          padding: '8px 14px', borderRadius: 9999,
          background: 'rgba(255,255,255,.06)', border: '1px solid var(--card-border)',
          color: 'var(--text)', cursor: 'pointer', fontSize: 12,
        }}>назад</button>
      </div>
    );
  }

  return (
    <div style={{ padding: '0 0 100px', position: 'relative' }}>
      <DecorLayer density={tweaks.decor * 0.4} seed={'d-' + f.id} palette={tweaks.palette} />

      {/* Header bar */}
      <div style={{
        position: 'sticky', top: 0, zIndex: 30,
        padding: '8px 16px', display: 'flex', justifyContent: 'space-between', gap: 8,
      }}>
        <IconChip onClick={goBack}>{I.back(20)}</IconChip>
        <div style={{ display: 'flex', gap: 8 }}>
          {!isOrderItem && (
            <IconChip onClick={() => toggleWishlist(f)} active={isWish}>
              {I.heart(18, isWish ? 'currentColor' : 'none')}
            </IconChip>
          )}
          <IconChip onClick={handleShare}>{I.share(18)}</IconChip>
        </div>
      </div>

      {/* Hero — main image + thumbnails */}
      <div style={{ padding: '6px 16px 0', position: 'relative', zIndex: 2 }}>
        <div
          style={{ position: 'relative', cursor: 'zoom-in' }}
          onTouchStart={(e) => { window.__nyaSwipe = e.touches[0].clientX; }}
          onTouchEnd={(e) => {
            const x0 = window.__nyaSwipe; if (x0 == null) return;
            const dx = e.changedTouches[0].clientX - x0;
            if (Math.abs(dx) > 40) (dx > 0 ? prevView : nextView)();
            window.__nyaSwipe = null;
          }}
        >
          <div onClick={() => setZoom(true)}>
            <FigureArt figure={f} view={view} height={300} style={{ borderRadius: 'var(--radius-lg)' }}/>
          </div>
          <div style={{
            position: 'absolute', bottom: 12, left: 12, display: 'flex', gap: 6,
          }}>
            <StatusPill status={f.status} />
          </div>
          {/* prev / next arrows */}
          <button onClick={(e) => { e.stopPropagation(); prevView(); }} style={arrowStyle('left')}>
            {I.back(20)}
          </button>
          <button onClick={(e) => { e.stopPropagation(); nextView(); }} style={arrowStyle('right')}>
            <span style={{ transform: 'rotate(180deg)', display: 'inline-flex' }}>{I.back(20)}</span>
          </button>
          {/* dots indicator */}
          <div style={{
            position: 'absolute', bottom: 12, right: 12,
            display: 'flex', gap: 4, padding: '6px 9px',
            background: 'rgba(20,12,40,.45)',
            backdropFilter: 'blur(8px)', WebkitBackdropFilter: 'blur(8px)',
            borderRadius: 9999,
          }}>
            {views.map((v, i) => (
              <span key={v} style={{
                width: i === viewIdx ? 14 : 5, height: 5, borderRadius: 9999,
                background: i === viewIdx ? 'var(--accent-a)' : 'rgba(255,255,255,.45)',
                transition: 'width .2s',
              }}/>
            ))}
          </div>
        </div>
        {/* thumbnail strip — scrolls horizontally when there are many photos */}
        <div className="nya-thumbs" style={{
          marginTop: 10, display: 'flex', gap: 8, overflowX: 'auto',
          paddingBottom: 4, scrollbarWidth: 'none',
        }}>
          {views.map((v, i) => (
            <button key={i} onClick={() => setViewIdx(i)} style={{
              padding: 0, border: 0, cursor: 'pointer',
              borderRadius: 'var(--radius-sm)', overflow: 'hidden',
              boxShadow: v === view
                ? `0 0 0 2px var(--accent-a), 0 0 14px var(--glow-soft)`
                : `0 0 0 1px var(--card-border)`,
              transition: 'box-shadow .15s',
              background: 'transparent',
              flex: '0 0 auto',
              width: 'calc((100% - 24px) / 4)', minWidth: 64,
            }}>
              <FigureArt figure={f} view={v} height={62} style={{ borderRadius: 0 }}/>
            </button>
          ))}
        </div>
      </div>

      {/* Title block */}
      <div style={{ padding: '16px 16px 0', position: 'relative', zIndex: 2 }}>
        <div style={{ fontSize: 11, color: 'var(--text-dim)', fontWeight: 600, textTransform: 'lowercase', letterSpacing: '.06em' }}>
          {f.maker} · {f.line}
        </div>
        <h1 style={{
          margin: '4px 0 0', fontFamily: 'Comfortaa, sans-serif', fontWeight: 600,
          fontSize: 26, lineHeight: 1.15, letterSpacing: '-.01em',
          background: 'linear-gradient(135deg, #fff 0%, var(--accent-a) 80%)',
          WebkitBackgroundClip: 'text', backgroundClip: 'text', color: 'transparent',
        }}>{f.name}</h1>

        {/* Price + relase row */}
        <div style={{ marginTop: 14, display: 'flex', gap: 10 }}>
          <GlassCard padding={12} style={{ flex: 1 }}>
            <div style={{ fontSize: 10.5, color: 'var(--text-dim)', fontWeight: 600, textTransform: 'lowercase' }}>цена</div>
            <div style={{ fontFamily: 'Comfortaa, sans-serif', fontWeight: 600, fontSize: 19, marginTop: 4 }}>
              {fmt(f.price)}
            </div>
          </GlassCard>
          <GlassCard padding={12} style={{ flex: 1 }}>
            <div style={{ fontSize: 10.5, color: 'var(--text-dim)', fontWeight: 600, textTransform: 'lowercase' }}>релиз</div>
            <div style={{ fontFamily: 'Comfortaa, sans-serif', fontWeight: 600, fontSize: 19, marginTop: 4 }}>
              {nyaFormatDate(f.release)}
            </div>
          </GlassCard>
        </div>

        {/* Payment progress */}
        {f.status === 'preorder' && (
          <GlassCard padding={14} style={{ marginTop: 10 }}>
            <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline' }}>
              <div style={{ fontSize: 12, color: 'var(--text-dim)', fontWeight: 600 }}>оплачено</div>
              <div style={{ fontSize: 12, color: 'var(--text)', fontWeight: 600 }}>
                {fmt(f.paid)} <span style={{ color: 'var(--text-dim)' }}>/ {fmt(f.price)}</span>
              </div>
            </div>
            <div style={{
              marginTop: 10, height: 8, borderRadius: 9999,
              background: 'rgba(255,255,255,.08)', overflow: 'hidden', position: 'relative',
            }}>
              <div style={{
                position: 'absolute', inset: 0,
                width: `${Math.max(4, progress * 100)}%`,
                background: 'linear-gradient(90deg, var(--accent-a), var(--accent-b))',
                borderRadius: 9999,
                boxShadow: '0 0 12px var(--glow-strong)',
                transition: 'width .4s',
              }}/>
            </div>
            {f.payDays > 0 && (
              <div style={{
                marginTop: 10, fontSize: 12, color: 'var(--text-dim)',
                display: 'flex', justifyContent: 'space-between',
              }}>
                <span>следующий платёж</span>
                <span style={{ color: f.payDays <= 3 ? '#ffb3c9' : 'var(--text)', fontWeight: 600 }}>
                  через {f.payDays} дн
                </span>
              </div>
            )}
          </GlassCard>
        )}

        {/* Description */}
        <SectionLabel>описание</SectionLabel>
        <GlassCard padding={14}>
          <p style={{
            margin: 0, fontSize: 13, lineHeight: 1.55,
            color: 'var(--text)', textWrap: 'pretty',
          }}>
            {description}
          </p>
        </GlassCard>

        {/* Tags — click any tag to open catalog filtered by it */}
        {f.tags && f.tags.length > 0 && (
          <>
            <SectionLabel>теги</SectionLabel>
            <div style={{ display: 'flex', flexWrap: 'wrap', gap: 6 }}>
              {f.tags.map(t => (
                <button key={t} onClick={() => openCatalogWithTag?.(t)} style={{
                  display: 'inline-flex', alignItems: 'center', gap: 3,
                  padding: '6px 12px', borderRadius: 9999,
                  background: 'rgba(255,255,255,.06)',
                  border: '1px solid var(--card-border)',
                  color: 'var(--text)',
                  fontFamily: 'Quicksand, sans-serif',
                  fontSize: 12, fontWeight: 500,
                  cursor: 'pointer',
                  transition: 'all .15s',
                }}
                onMouseEnter={(e) => {
                  e.currentTarget.style.background = 'linear-gradient(135deg, var(--accent-a), var(--accent-b))';
                  e.currentTarget.style.color = '#3a1e4d';
                  e.currentTarget.style.borderColor = 'transparent';
                  e.currentTarget.style.boxShadow = '0 0 10px var(--glow-soft)';
                }}
                onMouseLeave={(e) => {
                  e.currentTarget.style.background = 'rgba(255,255,255,.06)';
                  e.currentTarget.style.color = 'var(--text)';
                  e.currentTarget.style.borderColor = 'var(--card-border)';
                  e.currentTarget.style.boxShadow = 'none';
                }}>
                  <span style={{ opacity: .55 }}>#</span>{t}
                </button>
              ))}
            </div>
          </>
        )}

        {/* Price history chart */}
        <SectionLabel>история цены</SectionLabel>
        <GlassCard padding={14}>
          <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline', marginBottom: 8 }}>
            <span style={{ fontSize: 11, color: 'var(--text-dim)' }}>мин · {fmt(pMin)}</span>
            <span style={{ fontSize: 11, color: 'var(--text-dim)' }}>макс · {fmt(pMax)}</span>
          </div>
          <svg viewBox={`0 0 ${w} ${hCh}`} width="100%" height={hCh} style={{ display: 'block' }}>
            <defs>
              <linearGradient id="ph-fill" x1="0" y1="0" x2="0" y2="1">
                <stop offset="0%"   stopColor="var(--accent-a)" stopOpacity=".55"/>
                <stop offset="100%" stopColor="var(--accent-b)" stopOpacity="0"/>
              </linearGradient>
              <linearGradient id="ph-line" x1="0" y1="0" x2="1" y2="0">
                <stop offset="0%"   stopColor="var(--accent-a)"/>
                <stop offset="100%" stopColor="var(--accent-b)"/>
              </linearGradient>
            </defs>
            <path d={areaPath} fill="url(#ph-fill)"/>
            <path d={linePath} fill="none" stroke="url(#ph-line)" strokeWidth="2.5"
                  strokeLinecap="round" strokeLinejoin="round"
                  style={{ filter: 'drop-shadow(0 0 4px var(--glow-strong))' }}/>
            {pts.map((p, i) => (
              <circle key={i} cx={p.x} cy={p.y} r={i === pts.length - 1 ? 3.5 : 0} fill="#fff"
                      style={{ filter: 'drop-shadow(0 0 4px var(--glow-strong))' }}/>
            ))}
          </svg>
          <div style={{ display: 'flex', justifyContent: 'space-between', marginTop: 4, fontSize: 10, color: 'var(--text-dim)' }}>
            {pts.map((p, i) => <span key={i}>{p.m}</span>)}
          </div>
        </GlassCard>

        {/* Details list */}
        <SectionLabel>детали</SectionLabel>
        <GlassCard padding={0}>
          {[
            ['производитель', f.maker],
            ['масштаб', f.scale],
            ['линейка', f.line],
            ['материал', 'ПВХ, АБС-пластик'],
            ['высота', f.scale === '1/6' ? '~28 см' : f.scale === '1/7' ? '~24 см' : '~21 см'],
            ['магазин', 'AmiAmi'],
          ].map((row, i, all) => (
            <div key={row[0]} style={{
              display: 'flex', justifyContent: 'space-between', alignItems: 'center',
              padding: '12px 16px',
              borderBottom: i < all.length - 1 ? '1px solid rgba(200,155,240,.10)' : 'none',
              fontSize: 13,
            }}>
              <span style={{ color: 'var(--text-dim)' }}>{row[0]}</span>
              <span style={{ color: 'var(--text)', fontWeight: 500 }}>{row[1]}</span>
            </div>
          ))}
        </GlassCard>

        {/* Actions */}
        <SectionLabel>действия</SectionLabel>
        <GradientButton full size="lg" onClick={() => toast.show('открываю сайт магазина…')}
          icon={
            <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor"
                 strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
              <path d="M7 17 L 17 7"/><path d="M8 7 H 17 V 16"/>
            </svg>
          }>
          открыть на сайте{f.maker ? ' ' + f.maker.toLowerCase() : ''}
        </GradientButton>
        <GhostButton full size="md" onClick={() => setNotesOpen(true)} style={{ marginTop: 8 }}>
          мои заметки
        </GhostButton>
      </div>

      {/* Notes modal — the only user-mutable thing on this screen.
          Order data itself is parsed automatically from nin-nin-game. */}
      <NotesModal open={notesOpen} figure={f} onClose={() => setNotesOpen(false)} />

      {/* Full-screen lightbox */}
      {zoom && (
        <div
          onClick={() => setZoom(false)}
          onTouchStart={onTouchStart}
          onTouchMove={onTouchMove}
          onTouchEnd={onTouchEnd}
          style={{
            position: 'fixed', inset: 0, zIndex: 200,
            background: 'rgba(10,6,24,.92)',
            backdropFilter: 'blur(16px)', WebkitBackdropFilter: 'blur(16px)',
            display: 'flex', flexDirection: 'column',
            borderRadius: 56, overflow: 'hidden',
            animation: 'nya-fade-down .2s ease forwards',
          }}
        >
          {/* top bar */}
          <div style={{
            display: 'flex', alignItems: 'center', justifyContent: 'space-between',
            padding: '14px 16px',
          }}>
            <div>
              <div style={{ fontFamily: 'Comfortaa, sans-serif', fontWeight: 600, fontSize: 15 }}>
                {f.name}
              </div>
              <div style={{ fontSize: 11, color: 'var(--text-dim)', fontWeight: 500, marginTop: 2 }}>
                {viewIdx + 1} / {views.length}
              </div>
            </div>
            <button onClick={(e) => { e.stopPropagation(); setZoom(false); }} style={{
              width: 38, height: 38, borderRadius: '50%', border: 0,
              background: 'rgba(255,255,255,.08)', color: '#fff',
              display: 'flex', alignItems: 'center', justifyContent: 'center',
              cursor: 'pointer', padding: 0,
            }}>{I.close(18)}</button>
          </div>

          {/* big image with arrows */}
          <div style={{
            flex: 1, position: 'relative', padding: '0 16px',
            display: 'flex', alignItems: 'center', justifyContent: 'center',
            overflow: 'hidden',
          }} onClick={(e) => e.stopPropagation()}
             onWheel={onWheel}
             onDoubleClick={onDblClick}
             onMouseDown={onMouseDown}>
            <div style={{
              width: '100%', maxWidth: 340,
              transform: `translate(${pan.x}px, ${pan.y}px) scale(${scale})`,
              transformOrigin: 'center',
              transition: dragRef.current.down || pinchRef.current.d0 ? 'none' : 'transform .15s ease-out',
              cursor: scale > 1 ? 'grab' : 'zoom-in',
              userSelect: 'none',
            }}>
              <FigureArt figure={f} view={view} height={420}
                         style={{ borderRadius: 0 }}/>
            </div>
            <button onClick={(e) => { e.stopPropagation(); prevView(); }} style={arrowStyle('left')}>
              {I.back(22)}
            </button>
            <button onClick={(e) => { e.stopPropagation(); nextView(); }} style={arrowStyle('right')}>
              <span style={{ transform: 'rotate(180deg)', display: 'inline-flex' }}>{I.back(22)}</span>
            </button>

            {/* zoom controls */}
            <div style={{
              position: 'absolute', right: 14, bottom: 14,
              display: 'flex', flexDirection: 'column', gap: 6,
            }}>
              <button onClick={(e) => { e.stopPropagation(); setScale(s => Math.min(4, s + 0.5)); }} style={zoomBtnStyle}>＋</button>
              <button onClick={(e) => { e.stopPropagation(); setScale(1); setPan({x:0,y:0}); }} style={{ ...zoomBtnStyle, fontSize: 11, fontWeight: 600 }}>{Math.round(scale * 100)}%</button>
              <button onClick={(e) => { e.stopPropagation(); setScale(s => Math.max(1, s - 0.5)); }} style={zoomBtnStyle}>−</button>
            </div>
          </div>

          {/* bottom thumbnail strip — horizontally scrollable for galleries > 4 */}
          <div style={{
            padding: '14px 16px 22px',
            display: 'flex', gap: 8, overflowX: 'auto', scrollbarWidth: 'none',
          }} onClick={(e) => e.stopPropagation()}>
            {views.map((v, i) => (
              <button key={i} onClick={() => setViewIdx(i)} style={{
                padding: 0, border: 0, cursor: 'pointer',
                borderRadius: 'var(--radius-sm)', overflow: 'hidden',
                boxShadow: i === viewIdx
                  ? `0 0 0 2px var(--accent-a), 0 0 14px var(--glow-soft)`
                  : `0 0 0 1px rgba(255,255,255,.1)`,
                background: 'transparent',
                flex: '0 0 auto',
                width: 'calc((100% - 24px) / 4)', minWidth: 64,
              }}>
                <FigureArt figure={f} view={v} height={62} style={{ borderRadius: 0 }}/>
              </button>
            ))}
          </div>
        </div>
      )}
    </div>
  );
}

// ─────────────────────────────────────────────────────────────────────────────
// 3. Calendar
// ─────────────────────────────────────────────────────────────────────────────
function CalendarScreen({ go, tweaks }) {
  const figures = useFigures();
  const fmt = useFormat();
  const [openMonth, setOpenMonth] = React.useState('2026-06');
  const [filterOpen, setFilterOpen] = React.useState(false);
  const [filter, setFilter] = React.useState({
    statuses: new Set(),
    minAmount: 0,
    showPaid: false,
  });

  const filterCount = filter.statuses.size + (filter.minAmount > 0 ? 1 : 0) + (filter.showPaid ? 1 : 0);

  const byMonth = React.useMemo(() => {
    const grouped = {};
    figures.forEach(f => {
      if (f.status === 'delivered') return;
      if (!filter.showPaid && (f.status === 'paid' || f.status === 'shipped')) return;
      if (filter.statuses.size > 0 && !filter.statuses.has(f.status)) return;
      const remaining = f.price - f.paid;
      if (remaining < filter.minAmount) return;
      const d = new Date(f.release);
      const key = `${d.getFullYear()}-${String(d.getMonth()+1).padStart(2,'0')}`;
      if (!grouped[key]) grouped[key] = { key, date: d, items: [], total: 0 };
      grouped[key].items.push(f);
      grouped[key].total += remaining;
    });
    return Object.values(grouped).sort((a,b) => a.date - b.date);
  }, [filter]);

  return (
    <div style={{ padding: '0 16px 100px', position: 'relative' }}>
      <DecorLayer density={tweaks.decor * 0.4} seed="cal" palette={tweaks.palette} />

      <div style={{ position: 'relative', zIndex: 2 }}>
        <ScreenTitle
          subtitle="платежи помесячно"
          action={
            <button onClick={() => setFilterOpen(true)} style={{
              display: 'inline-flex', alignItems: 'center', gap: 6,
              height: 36, padding: '0 14px', borderRadius: 9999, border: 0,
              background: filterCount > 0
                ? 'linear-gradient(135deg, var(--accent-a), var(--accent-b))'
                : 'rgba(255,255,255,.06)',
              color: filterCount > 0 ? '#3a1e4d' : 'var(--text)',
              fontSize: 12.5, fontWeight: 600, cursor: 'pointer',
              border: '1px solid ' + (filterCount > 0 ? 'transparent' : 'var(--card-border)'),
              boxShadow: filterCount > 0 ? '0 0 10px var(--glow-soft)' : 'none',
            }}>
              {I.filter(14)} фильтр
              {filterCount > 0 && (
                <span style={{
                  width: 16, height: 16, borderRadius: '50%',
                  background: 'rgba(58,30,77,.25)', color: '#3a1e4d',
                  fontSize: 10, fontWeight: 700,
                  display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
                }}>{filterCount}</span>
              )}
            </button>
          }
        >календарь</ScreenTitle>

        {/* Summary card */}
        <GlassCard padding={14} glow style={{ marginBottom: 14 }}>
          <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
            <div>
              <div style={{ fontSize: 11, color: 'var(--text-dim)' }}>всего к оплате</div>
              <div style={{ fontFamily: 'Comfortaa, sans-serif', fontWeight: 600, fontSize: 22, marginTop: 2 }}>
                {fmt(byMonth.reduce((s, m) => s + m.total, 0))}
              </div>
            </div>
            <div style={{ width: 48, height: 48, position: 'relative' }}>
              <Moon size={48} />
            </div>
          </div>
        </GlassCard>

        {/* Months */}
        <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
          {byMonth.map(m => {
            const isOpen = openMonth === m.key;
            return (
              <GlassCard key={m.key} padding={0} style={{ position: 'relative' }}>
                <div
                  onClick={() => setOpenMonth(isOpen ? null : m.key)}
                  style={{
                    display: 'flex', alignItems: 'center', gap: 10,
                    padding: '14px 14px', cursor: 'pointer',
                  }}
                >
                  <div style={{
                    width: 42, height: 42, borderRadius: 14,
                    background: isOpen
                      ? 'linear-gradient(135deg, var(--accent-a), var(--accent-b))'
                      : 'rgba(255,255,255,.06)',
                    border: '1px solid var(--card-border)',
                    display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center',
                    boxShadow: isOpen ? '0 0 14px var(--glow-soft)' : 'none',
                    color: isOpen ? '#3a1e4d' : 'var(--text)',
                  }}>
                    <span style={{ fontSize: 9, fontWeight: 700, lineHeight: 1, textTransform: 'uppercase' }}>
                      {RU_MONTHS[m.date.getMonth()]}
                    </span>
                    <span style={{ fontFamily: 'Comfortaa, sans-serif', fontWeight: 700, fontSize: 16, lineHeight: 1.1 }}>
                      {String(m.date.getFullYear()).slice(2)}
                    </span>
                  </div>
                  <div style={{ flex: 1 }}>
                    <div style={{ fontFamily: 'Comfortaa, sans-serif', fontWeight: 600, fontSize: 14, textTransform: 'lowercase' }}>
                      {RU_MONTHS_FULL[m.date.getMonth()]}
                    </div>
                    <div style={{ fontSize: 11, color: 'var(--text-dim)', marginTop: 2 }}>
                      {m.items.length} {ruPlural(m.items.length, ['позиция', 'позиции', 'позиций'])}
                    </div>
                  </div>
                  <div style={{ textAlign: 'right' }}>
                    <div style={{ fontFamily: 'Comfortaa, sans-serif', fontWeight: 600, fontSize: 14 }}>
                      {fmt(m.total)}
                    </div>
                  </div>
                  <span style={{
                    color: 'var(--text-dim)',
                    transform: isOpen ? 'rotate(180deg)' : 'none',
                    transition: 'transform .2s',
                    display: 'flex',
                  }}>{I.chevDown(16)}</span>
                </div>

                {isOpen && (
                  <div style={{
                    padding: '0 14px 14px',
                    display: 'flex', flexDirection: 'column', gap: 8,
                    animation: 'nya-fade-down .25s ease forwards',
                  }}>
                    {m.items.map(f => (
                      <div key={f.id} onClick={() => go('detail', f)} style={{
                        display: 'flex', alignItems: 'center', gap: 10,
                        padding: '8px', borderRadius: 'var(--radius-sm)',
                        background: 'rgba(255,255,255,.04)',
                        cursor: 'pointer',
                      }}>
                        <div style={{ width: 44, height: 44, flexShrink: 0 }}>
                          <FigureArt figure={f} height={44} style={{ borderRadius: 12 }} />
                        </div>
                        <div style={{ flex: 1, minWidth: 0 }}>
                          <div style={{ fontFamily: 'Comfortaa, sans-serif', fontWeight: 600, fontSize: 13,
                                        overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
                            {f.name}
                          </div>
                          <div style={{ fontSize: 10.5, color: 'var(--text-dim)', marginTop: 1 }}>
                            {nyaFormatDate(f.release)} · {f.line}
                          </div>
                        </div>
                        <div style={{ textAlign: 'right' }}>
                          <div style={{ fontFamily: 'Comfortaa, sans-serif', fontWeight: 600, fontSize: 13 }}>
                            {fmt(f.price - f.paid)}
                          </div>
                          <div style={{ fontSize: 10, color: 'var(--text-dim)', marginTop: 1 }}>
                            <StatusPill status={f.status} size="sm" />
                          </div>
                        </div>
                      </div>
                    ))}
                  </div>
                )}
              </GlassCard>
            );
          })}
        </div>
      </div>

      <CalendarFilterModal
        open={filterOpen}
        onClose={() => setFilterOpen(false)}
        value={filter}
        onApply={setFilter}
      />
    </div>
  );
}
// ─────────────────────────────────────────────────────────────────────────────
// Helper hook — turns a container into a drag-to-scroll horizontal carousel.
// Wheel events also scroll horizontally (no Shift required) — much better UX
// for desktop users than relying on browser-default horizontal scroll.
function useDragScroll() {
  const ref = React.useRef(null);
  React.useEffect(() => {
    const el = ref.current; if (!el) return;
    let down = false, sx = 0, sl = 0, moved = false;
    const onDown = (e) => {
      down = true; moved = false;
      sx = e.clientX; sl = el.scrollLeft;
      el.style.cursor = 'grabbing'; el.style.userSelect = 'none';
    };
    const onMove = (e) => {
      if (!down) return;
      const dx = e.clientX - sx;
      if (Math.abs(dx) > 3) moved = true;
      el.scrollLeft = sl - dx;
    };
    const onUp = () => {
      down = false; el.style.cursor = 'grab'; el.style.userSelect = '';
    };
    const onClick = (e) => { if (moved) { e.stopPropagation(); e.preventDefault(); } };
    const onWheel = (e) => {
      // translate vertical wheel to horizontal scroll (common pattern for h-rails)
      if (Math.abs(e.deltaY) > Math.abs(e.deltaX)) {
        el.scrollLeft += e.deltaY;
        e.preventDefault();
      }
    };
    el.style.cursor = 'grab';
    el.addEventListener('mousedown', onDown);
    window.addEventListener('mousemove', onMove);
    window.addEventListener('mouseup', onUp);
    el.addEventListener('click', onClick, true);
    el.addEventListener('wheel', onWheel, { passive: false });
    return () => {
      el.removeEventListener('mousedown', onDown);
      window.removeEventListener('mousemove', onMove);
      window.removeEventListener('mouseup', onUp);
      el.removeEventListener('click', onClick, true);
      el.removeEventListener('wheel', onWheel);
    };
  }, []);
  return ref;
}

// Visual hint: nothing — just a smooth scrollable container with drag + wheel.
function HScrollRail({ children, padding = '14px 16px' }) {
  const ref = useDragScroll();
  return (
    <div ref={ref} style={{
      display: 'flex', gap: 8, padding,
      overflowX: 'auto', overflowY: 'hidden',
      scrollbarWidth: 'none',
      WebkitOverflowScrolling: 'touch',
      scrollBehavior: 'smooth',
      touchAction: 'pan-x',
      overflowAnchor: 'none',
    }}>
      <style>{`
        .nya-rail-hidescroll::-webkit-scrollbar { display: none; }
      `}</style>
      {children}
    </div>
  );
}

function SearchScreen({ go, tweaks, wishlist, toggleWishlist, tags, setTags }) {
  const figures = useFigures();
  const [q, setQ] = React.useState('');
  const [category, setCategory] = React.useState('all');
  const [quick, setQuick] = React.useState(null); // 'just-added' | 'hot-pre' | 'hot' | null
  const [sortDesc, setSortDesc] = React.useState(false);
  const [filterOpen, setFilterOpen] = React.useState(false);
  const [advFilter, setAdvFilter] = React.useState({
    priceMin: 0, priceMax: 350,
    statuses: new Set(), makers: new Set(), year: 'all',
  });

  const toggleTag = (t) => setTags(prev => {
    const n = new Set(prev);
    if (n.has(t)) n.delete(t); else n.add(t);
    return n;
  });

  // Aggregate all tags from the catalog with usage counts → popular tag cloud.
  const allTags = React.useMemo(() => {
    const counts = {};
    figures.forEach(f => f.tags?.forEach(t => { counts[t] = (counts[t] || 0) + 1; }));
    return Object.entries(counts).sort((a, b) => b[1] - a[1]);
  }, []);

  // Tag suggestions while typing — match the input against tag names
  const tagSuggestions = React.useMemo(() => {
    if (!q || q.length < 1) return [];
    const s = q.toLowerCase();
    return allTags.filter(([t]) => t.includes(s) && !tags.has(t)).slice(0, 5);
  }, [q, allTags, tags]);

  // ── Catalog fetching ──
  // When the user types a real query (>= 2 chars) or selects one of the
  // catalog-style chips, hit /api/catalog/* instead of filtering the
  // local figures array. Local filtering stays the default when there's
  // nothing to query.
  const [debouncedQ, setDebouncedQ] = React.useState('');
  const [catalogItems, setCatalogItems] = React.useState([]);
  const [catalogLoading, setCatalogLoading] = React.useState(false);
  const [catalogPage, setCatalogPage] = React.useState(1);
  const [catalogTotalPages, setCatalogTotalPages] = React.useState(1);
  const [catalogTotalResults, setCatalogTotalResults] = React.useState(0);
  const initData = React.useMemo(() => window.Telegram?.WebApp?.initData || null, []);

  React.useEffect(() => {
    const id = setTimeout(() => setDebouncedQ(q.trim()), 350);
    return () => clearTimeout(id);
  }, [q]);

  // Catalog tab is ALWAYS catalog mode — user comes here to browse Nin-Nin
  // Game, not to filter their own collection. Defaults to "new arrivals"
  // when no query/chip is set so the tab is never empty.
  const isCatalogMode = !!initData;

  // Whenever the filter (query/chip/sort) changes, reset to page 1 so
  // the fetch runs a fresh batch instead of appending to a stale set.
  React.useEffect(() => { setCatalogPage(1); }, [debouncedQ, quick, sortDesc]);

  React.useEffect(() => {
    if (!isCatalogMode) {
      setCatalogItems([]);
      setCatalogLoading(false);
      return;
    }
    let url;
    if (debouncedQ.length >= 2) {
      url = '/api/catalog/search?q=' + encodeURIComponent(debouncedQ);
    } else if (quick === 'hot-pre' || quick === 'hot') {
      url = '/api/catalog/hot';
    } else {
      url = '/api/catalog/new'; // default + 'just-added' chip
    }
    url += (url.includes('?') ? '&' : '?') + 'page=' + catalogPage;
    url += '&sort=price&order=' + (sortDesc ? 'desc' : 'asc');

    let cancelled = false;
    setCatalogLoading(true);
    fetch(url, { headers: { Authorization: 'tma ' + initData } })
      .then(r => r.ok ? r.json() : Promise.reject(new Error('HTTP ' + r.status)))
      .then(data => {
        if (cancelled) return;
        const items = Array.isArray(data.items) ? data.items : [];
        if (catalogPage === 1) setCatalogItems(items);
        else setCatalogItems(prev => [...prev, ...items]);
        setCatalogTotalPages(data.total_pages || 1);
        setCatalogTotalResults(data.total_results || items.length);
      })
      .catch(e => {
        console.error('catalog fetch failed', e);
        if (!cancelled && catalogPage === 1) setCatalogItems([]);
      })
      .finally(() => { if (!cancelled) setCatalogLoading(false); });
    return () => { cancelled = true; };
  }, [isCatalogMode, debouncedQ, quick, catalogPage, sortDesc, initData]);

  const loadMoreCatalog = React.useCallback(() => {
    setCatalogPage(p => p + 1);
  }, []);
  const hasMoreCatalog = isCatalogMode && catalogPage < catalogTotalPages;

  const results = React.useMemo(() => {
    const source = isCatalogMode ? (catalogItems || []) : (figures || []);
    let r = source.filter(f => {
      if (category !== 'all' && f.category !== category) return false;
      // local-only filters (tag/text/quick) skipped in catalog mode —
      // server already did the search and chip routing
      if (!isCatalogMode) {
        if (quick) {
          const qf = NYA_QUICK_FILTERS.find(x => x.id === quick);
          if (qf && !qf.filter(f)) return false;
        }
        if (tags.size > 0) {
          for (const t of tags) if (!(f.tags || []).includes(t)) return false;
        }
        if (q) {
          const s = q.toLowerCase();
          const hay = [f.name, f.line, f.maker, ...(f.tags || [])].join(' ').toLowerCase();
          if (!hay.includes(s)) return false;
        }
      }
      if (advFilter.priceMin > 0 || advFilter.priceMax < 350) {
        if (f.price != null && (f.price < advFilter.priceMin || f.price > advFilter.priceMax)) return false;
      }
      if (advFilter.statuses.size > 0 && !advFilter.statuses.has(f.status)) return false;
      if (advFilter.makers.size > 0 && f.maker && !advFilter.makers.has(f.maker)) return false;
      if (advFilter.year !== 'all' && (!f.release || !f.release.startsWith(advFilter.year))) return false;
      return true;
    });
    r.sort((a, b) => sortDesc ? (b.price || 0) - (a.price || 0) : (a.price || 0) - (b.price || 0));
    return r;
  }, [isCatalogMode, catalogItems, figures, q, category, quick, tags, sortDesc, advFilter]);

  const advCount = (advFilter.priceMin > 0 ? 1 : 0)
                 + (advFilter.priceMax < 350 ? 1 : 0)
                 + advFilter.statuses.size + advFilter.makers.size
                 + (advFilter.year !== 'all' ? 1 : 0);
  const hasAnyFilter = q || category !== 'all' || quick || tags.size > 0 || advCount > 0;
  const clearAll = () => {
    setQ(''); setCategory('all'); setQuick(null); setTags(new Set());
    setAdvFilter({ priceMin: 0, priceMax: 350, statuses: new Set(), makers: new Set(), year: 'all' });
  };

  return (
    <div style={{ padding: '0 0 100px', position: 'relative' }}>
      <DecorLayer density={tweaks.decor * 0.3} seed="search" palette={tweaks.palette} />

      <div style={{ position: 'relative', zIndex: 2 }}>
        <div style={{ padding: '0 16px' }}>
          <ScreenTitle subtitle="по тегам, категориям и подборкам">каталог</ScreenTitle>

          <NyaInput
            value={q}
            onChange={setQ}
            placeholder="название, тег или производитель…"
            icon={I.search(18)}
          />

          {/* Tag suggestions dropdown while typing */}
          {tagSuggestions.length > 0 && (
            <GlassCard padding={6} style={{ marginTop: 8 }}>
              {tagSuggestions.map(([t, c]) => (
                <button key={t} onClick={() => { toggleTag(t); setQ(''); }} style={{
                  display: 'flex', alignItems: 'center', justifyContent: 'space-between',
                  width: '100%', padding: '8px 10px',
                  background: 'transparent', border: 0, borderRadius: 'var(--radius-sm)',
                  color: 'var(--text)', fontFamily: 'Quicksand, sans-serif',
                  fontSize: 13, fontWeight: 500, cursor: 'pointer', textAlign: 'left',
                }}>
                  <span style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
                    <span style={{ color: 'var(--accent-a)' }}>#</span>{t}
                  </span>
                  <span style={{ fontSize: 11, color: 'var(--text-dim)' }}>{c}</span>
                </button>
              ))}
            </GlassCard>
          )}

          {/* Quick filter rail — Just added / Hot pre-orders / Hot products */}
          <SectionLabel>подборки</SectionLabel>
        </div>
        <HScrollRail>
          {NYA_QUICK_FILTERS.map(qf => {
            const on = quick === qf.id;
            return (
              <button key={qf.id} onClick={() => setQuick(on ? null : qf.id)} style={{
                flexShrink: 0,
                padding: '10px 16px', borderRadius: 'var(--radius-sm)',
                background: on
                  ? 'linear-gradient(135deg, var(--accent-a), var(--accent-b))'
                  : 'rgba(255,255,255,.06)',
                border: '1px solid ' + (on ? 'transparent' : 'var(--card-border)'),
                color: on ? '#3a1e4d' : 'var(--text)',
                fontFamily: 'Comfortaa, sans-serif', fontWeight: 600, fontSize: 12.5,
                cursor: 'pointer', whiteSpace: 'nowrap',
                boxShadow: on ? '0 0 14px var(--glow-soft)' : 'none',
                transition: 'all .15s',
              }}>{qf.label}</button>
            );
          })}
        </HScrollRail>

        {/* Categories — horizontal scroll, icon + label */}
        <div style={{ padding: '0 16px' }}>
          <SectionLabel>категория</SectionLabel>
        </div>
        <HScrollRail>
          {[{ id: 'all', label: 'все' }, ...NYA_CATEGORIES].map(c => {
            const on = category === c.id;
            const renderIcon = NYA_CAT_ICONS[c.id] || NYA_CAT_ICONS.all;
            return (
              <button key={c.id} onClick={() => setCategory(c.id)} style={{
                flexShrink: 0,
                display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center',
                gap: 8,
                width: 78, height: 76, padding: '8px 4px',
                borderRadius: 'var(--radius-sm)',
                background: on
                  ? 'rgba(255,158,205,.18)'
                  : 'rgba(255,255,255,.04)',
                border: '1px solid ' + (on ? 'var(--accent-a)' : 'var(--card-border)'),
                color: on ? 'var(--accent-a)' : 'var(--text-dim)',
                cursor: 'pointer',
                boxShadow: on ? '0 0 14px var(--glow-soft)' : 'none',
                transition: 'all .15s',
              }}>
                <span style={{ display: 'flex', filter: on ? 'drop-shadow(0 0 5px var(--glow-strong))' : 'none' }}>
                  {renderIcon(22)}
                </span>
                <span style={{
                  fontFamily: 'Quicksand, sans-serif', fontSize: 10.5, fontWeight: 600,
                  textAlign: 'center', lineHeight: 1.1,
                }}>{c.label}</span>
              </button>
            );
          })}
        </HScrollRail>

        {/* Active tags — appear when user added tags from a figure-detail click */}
        {tags.size > 0 && (
          <div style={{ padding: '12px 16px 0' }}>
            <div style={{
              display: 'flex', alignItems: 'center', justifyContent: 'space-between',
              marginBottom: 6,
            }}>
              <span style={{
                fontSize: 11, fontWeight: 600, color: 'var(--text-dim)',
                letterSpacing: '.08em', textTransform: 'uppercase',
              }}>активные теги</span>
              <button onClick={() => setTags(new Set())} style={{
                background: 'transparent', border: 0, padding: 4,
                color: 'var(--accent-a)', fontSize: 11, fontWeight: 600, cursor: 'pointer',
              }}>сбросить</button>
            </div>
            <div style={{ display: 'flex', flexWrap: 'wrap', gap: 6 }}>
              {[...tags].map(t => (
                <span key={t} style={{
                  display: 'inline-flex', alignItems: 'center', gap: 6,
                  padding: '5px 6px 5px 11px', borderRadius: 9999,
                  background: 'linear-gradient(135deg, var(--accent-a), var(--accent-b))',
                  color: '#3a1e4d', fontSize: 11.5, fontWeight: 600,
                  boxShadow: '0 0 10px var(--glow-soft)',
                }}>
                  <span style={{ opacity: .7 }}>#</span>{t}
                  <button onClick={() => toggleTag(t)} style={{
                    width: 16, height: 16, borderRadius: '50%', border: 0,
                    background: 'rgba(58,30,77,.25)', color: '#3a1e4d',
                    display: 'flex', alignItems: 'center', justifyContent: 'center',
                    cursor: 'pointer', padding: 0,
                  }}>{I.close(10)}</button>
                </span>
              ))}
            </div>
          </div>
        )}

        {/* Sort + reset row */}
        <div style={{
          display: 'flex', justifyContent: 'space-between', alignItems: 'center',
          padding: '14px 16px 8px',
        }}>
          <div style={{ fontSize: 12, color: 'var(--text-dim)' }}>
            найдено: <span style={{ color: 'var(--text)', fontWeight: 600 }}>{isCatalogMode ? (catalogTotalResults || results.length) : results.length}</span>
            {hasAnyFilter && (
              <button onClick={clearAll} style={{
                marginLeft: 10, background: 'transparent', border: 0, padding: 0,
                color: 'var(--text-dim)', fontSize: 11, fontWeight: 500, cursor: 'pointer',
                textDecoration: 'underline',
              }}>очистить</button>
            )}
          </div>
          <div style={{ display: 'flex', gap: 8, alignItems: 'center' }}>
            <button onClick={() => setFilterOpen(true)} style={{
              display: 'inline-flex', alignItems: 'center', gap: 6,
              padding: '6px 12px', borderRadius: 9999, border: 0,
              background: advCount > 0
                ? 'linear-gradient(135deg, var(--accent-a), var(--accent-b))'
                : 'rgba(255,255,255,.06)',
              color: advCount > 0 ? '#3a1e4d' : 'var(--text)',
              fontSize: 12, fontWeight: 600, cursor: 'pointer',
              border: '1px solid ' + (advCount > 0 ? 'transparent' : 'var(--card-border)'),
              boxShadow: advCount > 0 ? '0 0 10px var(--glow-soft)' : 'none',
            }}>
              {I.filter(14)} фильтр
              {advCount > 0 && (
                <span style={{
                  width: 16, height: 16, borderRadius: '50%',
                  background: 'rgba(58,30,77,.25)', color: '#3a1e4d',
                  fontSize: 10, fontWeight: 700,
                  display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
                }}>{advCount}</span>
              )}
            </button>
            <button onClick={() => setSortDesc(s => !s)} style={{
              display: 'inline-flex', alignItems: 'center', gap: 4,
              background: 'transparent', border: 0, padding: 4,
              color: 'var(--accent-a)', fontSize: 12, fontWeight: 600,
              cursor: 'pointer',
            }}>
              цена {sortDesc ? '↓' : '↑'}
            </button>
          </div>
        </div>

        <div style={{ padding: '0 16px' }}>
          {catalogLoading && results.length === 0 ? (
            <CatalogSkeleton />
          ) : results.length === 0 ? (
            <EmptyState
              title="ничего не нашлось"
              text={isCatalogMode ? "попробуй другой запрос или сбрось фильтры" : "попробуй другие теги или сбрось фильтры"}
              cta={<GhostButton onClick={clearAll}>сбросить всё</GhostButton>}
            />
          ) : (
            <div style={{ display: 'grid', gridTemplateColumns: 'repeat(2, minmax(0, 1fr))', gap: 10 }}>
              {results.map(f => {
                const isWish = (f.product_url && wishlist.has(f.product_url)) || f.wishlist;
                return (
                  <div key={f.id} style={{ position: 'relative' }}>
                    <FigureCard figure={f} onClick={() => go('detail', f)} />
                    <button onClick={(e) => { e.stopPropagation(); toggleWishlist(f); }} style={{
                      position: 'absolute', top: 16, right: 16,
                      width: 30, height: 30, borderRadius: '50%', border: 0,
                      background: 'rgba(20,12,40,.55)',
                      backdropFilter: 'blur(8px)', WebkitBackdropFilter: 'blur(8px)',
                      color: isWish ? 'var(--accent-a)' : 'rgba(255,255,255,.7)',
                      display: 'flex', alignItems: 'center', justifyContent: 'center',
                      cursor: 'pointer',
                      boxShadow: isWish ? '0 0 10px var(--glow-strong)' : 'none',
                    }}>
                      {I.heart(15, isWish ? 'currentColor' : 'none')}
                    </button>
                  </div>
                );
              })}
            </div>
          )}

          {hasMoreCatalog && results.length > 0 && (
            <div style={{ marginTop: 20, display: 'flex', justifyContent: 'center' }}>
              <GhostButton onClick={loadMoreCatalog} disabled={catalogLoading}>
                {catalogLoading ? 'загружаем…' : `показать ещё (стр ${catalogPage}/${catalogTotalPages})`}
              </GhostButton>
            </div>
          )}
        </div>
      </div>

      <CatalogFilterModal
        open={filterOpen}
        onClose={() => setFilterOpen(false)}
        value={advFilter}
        onApply={setAdvFilter}
      />
    </div>
  );
}
// ─────────────────────────────────────────────────────────────────────────────
function WishlistScreen({ go, tweaks, wishlist, toggleWishlist }) {
  const fmt = useFormat();
  const figures = useFigures();
  const items = figures.filter(f => (f.product_url && wishlist.has(f.product_url)) || f.wishlist);
  const total = items.reduce((s, f) => s + f.price, 0);

  return (
    <div style={{ padding: '0 16px 100px', position: 'relative' }}>
      <DecorLayer density={tweaks.decor * 0.5} seed="wish" palette={tweaks.palette} />

      <div style={{ position: 'relative', zIndex: 2 }}>
        <ScreenTitle subtitle={`${items.length} ${ruPlural(items.length, ['мечта', 'мечты', 'мечт'])}`}>
          вишлист
        </ScreenTitle>

        {items.length === 0 ? (
          <EmptyState
            title="пока пусто"
            text="добавь фигурки сердечком из каталога"
            cta={<GradientButton onClick={() => go('search')} icon={I.search(14)}>в каталог</GradientButton>}
          />
        ) : (
          <>
            <GlassCard padding={14} style={{ marginBottom: 14 }}>
              <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
                <div>
                  <div style={{ fontSize: 11, color: 'var(--text-dim)' }}>стоимость мечт</div>
                  <div style={{ fontFamily: 'Comfortaa, sans-serif', fontWeight: 600, fontSize: 22, marginTop: 2,
                                background: 'linear-gradient(135deg, #fff, var(--accent-a))',
                                WebkitBackgroundClip: 'text', backgroundClip: 'text', color: 'transparent' }}>
                    {fmt(total)}
                  </div>
                </div>
                <Heart size={32} color="var(--accent-a)" opacity={1} />
              </div>
            </GlassCard>

            <div style={{ display: 'grid', gridTemplateColumns: 'repeat(2, minmax(0, 1fr))', gap: 10 }}>
              {items.map(f => (
                <div key={f.id} style={{ position: 'relative' }}>
                  <FigureCard figure={f} onClick={() => go('detail', f)} />
                  <button onClick={(e) => { e.stopPropagation(); toggleWishlist(f); }} style={{
                    position: 'absolute', top: 16, right: 16,
                    width: 30, height: 30, borderRadius: '50%', border: 0,
                    background: 'rgba(20,12,40,.55)',
                    backdropFilter: 'blur(8px)', WebkitBackdropFilter: 'blur(8px)',
                    color: 'var(--accent-a)',
                    display: 'flex', alignItems: 'center', justifyContent: 'center',
                    cursor: 'pointer',
                    boxShadow: '0 0 10px var(--glow-strong)',
                  }}>
                    {I.heart(15, 'currentColor')}
                  </button>
                </div>
              ))}
            </div>
          </>
        )}
      </div>
    </div>
  );
}

// ─────────────────────────────────────────────────────────────────────────────
// 6. Settings
// ─────────────────────────────────────────────────────────────────────────────
function SettingsScreen({ go, tweaks, currency, setCurrency }) {
  const figures = useFigures();
  const settingsTgEnv = React.useMemo(() => {
    const tg = window.Telegram && window.Telegram.WebApp;
    return tg && tg.initData ? { inTelegram: true, initData: tg.initData } : { inTelegram: false };
  }, []);
  const [settingsMembers, setSettingsMembers] = React.useState([]);
  React.useEffect(() => {
    if (!settingsTgEnv.inTelegram) return;
    let cancelled = false;
    fetch('/api/workspace/members', { headers: { Authorization: 'tma ' + settingsTgEnv.initData } })
      .then(r => r.ok ? r.json() : Promise.reject(new Error('HTTP ' + r.status)))
      .then(data => { if (!cancelled) setSettingsMembers(data.members || []); })
      .catch(e => console.error('settings.members fetch failed', e));
    return () => { cancelled = true; };
  }, [settingsTgEnv]);
  const _roleLabel = (r) => r === 'owner' ? 'владелец' : r === 'editor' ? 'редактор' : 'наблюдатель';
  const [reminders, setReminders] = React.useState(['7d', '1d']);
  const [exportOpen, setExportOpen] = React.useState(false);
  const [confirmLogout, setConfirmLogout] = React.useState(false);
  const toast = useToast();
  const tgUser = React.useMemo(() => getTgUser(), []);

  const reminderOpts = [
    { id: '14d', label: 'за 14 дн' },
    { id: '7d',  label: 'за 7 дн' },
    { id: '3d',  label: 'за 3 дн' },
    { id: '1d',  label: 'за день' },
  ];
  const currencies = Object.entries(NYA_CURRENCIES).map(([id, c]) => ({ id, label: c.label }));

  return (
    <div style={{ padding: '0 16px 100px', position: 'relative' }}>
      <DecorLayer density={tweaks.decor * 0.25} seed="set" palette={tweaks.palette} />

      <div style={{ position: 'relative', zIndex: 2 }}>
        <ScreenTitle subtitle="настрой как уютно">настройки</ScreenTitle>

        {/* Profile */}
        <GlassCard padding={14} style={{ marginBottom: 14, display: 'flex', alignItems: 'center', gap: 12, cursor: 'pointer' }}
          onClick={() => go('profile')}>
          <TgAvatar user={tgUser} size={56} />
          <div style={{ flex: 1, minWidth: 0 }}>
            <div style={{ fontFamily: 'Comfortaa, sans-serif', fontWeight: 600, fontSize: 16,
                          overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
              {tgUser.name}
            </div>
            <div style={{ fontSize: 12, color: 'var(--text-dim)', marginTop: 2,
                          overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
              {tgUser.username ? '@' + tgUser.username : 'без юзернейма'} · {figures.length} {ruPlural(figures.length, ['фигурка', 'фигурки', 'фигурок'])}
            </div>
          </div>
          <IconChip onClick={() => go('profile')}>{I.chevRight(16)}</IconChip>
        </GlassCard>

        <SettingsSection label="валюта">
          <div style={{ display: 'flex', gap: 6 }}>
            {currencies.map(c => {
              const on = currency === c.id;
              return (
                <button key={c.id} onClick={() => setCurrency(c.id)} style={{
                  flex: 1, padding: '10px 4px', borderRadius: 'var(--radius-sm)',
                  background: on ? 'linear-gradient(135deg, var(--accent-a), var(--accent-b))' : 'rgba(255,255,255,.06)',
                  border: '1px solid ' + (on ? 'transparent' : 'var(--card-border)'),
                  color: on ? '#3a1e4d' : 'var(--text)',
                  fontFamily: 'Quicksand, sans-serif', fontWeight: 600, fontSize: 12,
                  cursor: 'pointer',
                  boxShadow: on ? '0 0 14px var(--glow-soft)' : 'none',
                }}>{c.label}</button>
              );
            })}
          </div>
        </SettingsSection>

        <SettingsSection label="напоминания об оплате">
          <div style={{ display: 'grid', gridTemplateColumns: 'repeat(2, minmax(0, 1fr))', gap: 6 }}>
            {reminderOpts.map(r => {
              const on = reminders.includes(r.id);
              return (
                <button key={r.id} onClick={() => setReminders(s => on ? s.filter(x => x !== r.id) : [...s, r.id])} style={{
                  display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 6,
                  padding: '10px 12px', borderRadius: 'var(--radius-sm)',
                  background: on ? 'rgba(255,158,205,.16)' : 'rgba(255,255,255,.04)',
                  border: '1px solid ' + (on ? 'var(--accent-a)' : 'var(--card-border)'),
                  color: 'var(--text)',
                  fontFamily: 'Quicksand, sans-serif', fontWeight: 500, fontSize: 12.5,
                  cursor: 'pointer',
                  boxShadow: on ? '0 0 10px var(--glow-soft)' : 'none',
                }}>
                  <span>{r.label}</span>
                  {on && <span style={{ color: 'var(--accent-a)' }}>{I.check(14)}</span>}
                </button>
              );
            })}
          </div>
        </SettingsSection>

        <SettingsSection label="уведомления">
          <GlassCard padding={0}>
            <SimpleRow label="настройки уведомлений" sub="типы, каналы, тихие часы"
              onClick={() => go('notifSettings')} last />
          </GlassCard>
        </SettingsSection>

        <SettingsSection label="участники">
          <GlassCard padding={0}>
            {settingsMembers.length === 0 ? (
              <div style={{ padding: '12px 16px', fontSize: 12, color: 'var(--text-dim)' }}>
                загружаем участников…
              </div>
            ) : settingsMembers.map(m => {
              const myTg = window.Telegram?.WebApp?.initDataUnsafe?.user || {};
              const photo = (m.is_me ? (myTg.photo_url || m.photo_url) : m.photo_url) || null;
              return (
                <MemberRow
                  key={m.user_id}
                  name={(m.name || ('id ' + m.user_id)) + (m.is_me ? ' · ты' : '')}
                  role={_roleLabel(m.role)}
                  photo={photo}
                />
              );
            })}
            <div onClick={() => go('members')} style={{
              display: 'flex', alignItems: 'center', gap: 10,
              padding: '14px 16px', cursor: 'pointer', color: 'var(--accent-a)',
            }}>
              <div style={{
                width: 34, height: 34, borderRadius: '50%',
                border: '1.5px dashed var(--accent-a)',
                display: 'flex', alignItems: 'center', justifyContent: 'center',
              }}>{I.plus(16)}</div>
              <div style={{ flex: 1, fontSize: 13.5, fontWeight: 500 }}>управлять участниками</div>
              {I.chevRight(14)}
            </div>
          </GlassCard>
        </SettingsSection>

        <SettingsSection label="приложение">
          <GlassCard padding={0}>
            <SimpleRow label="экспорт в csv" onClick={() => setExportOpen(true)} />
            <SimpleRow label="о приложении" onClick={() => go('about')} />
            <SimpleRow label="выйти" danger last onClick={() => setConfirmLogout(true)} />
          </GlassCard>
        </SettingsSection>

        <div style={{ textAlign: 'center', marginTop: 24, fontSize: 11, color: 'var(--text-dim)' }}>
          NyaShelf · v1.0
          <div style={{ marginTop: 6, display: 'flex', justifyContent: 'center', gap: 4 }}>
            <Heart size={10} color="var(--accent-a)" opacity={.8} /> сделано с любовью
          </div>
        </div>
      </div>

      {/* Modals */}
      <ExportCsvModal
        open={exportOpen}
        onClose={() => setExportOpen(false)}
        onExport={() => toast.show('csv отправлен в telegram', { kind: 'success' })}
      />
      <ConfirmDialog
        open={confirmLogout}
        title="выйти?"
        message="данные останутся на сервере. зайти можно будет повторно через любого участника."
        confirmLabel="выйти" danger
        onConfirm={() => { setConfirmLogout(false); toast.show('вышли из приложения'); }}
        onCancel={() => setConfirmLogout(false)}
      />
    </div>
  );
}

function SettingsSection({ label, children }) {
  return (
    <div style={{ marginBottom: 16 }}>
      <SectionLabel>{label}</SectionLabel>
      {children}
    </div>
  );
}

function SettingRow({ label, sub, value, onToggle, last }) {
  return (
    <div style={{
      display: 'flex', alignItems: 'center', gap: 10,
      padding: '14px 16px',
      borderBottom: last ? 'none' : '1px solid rgba(200,155,240,.10)',
    }}>
      <div style={{ flex: 1 }}>
        <div style={{ fontSize: 13.5, fontWeight: 500 }}>{label}</div>
        {sub && <div style={{ fontSize: 11, color: 'var(--text-dim)', marginTop: 1 }}>{sub}</div>}
      </div>
      <button onClick={() => onToggle(!value)} style={{
        position: 'relative', width: 44, height: 26, borderRadius: 9999, border: 0,
        background: value ? 'linear-gradient(135deg, var(--accent-a), var(--accent-b))' : 'rgba(255,255,255,.12)',
        boxShadow: value ? '0 0 10px var(--glow-soft)' : 'none',
        cursor: 'pointer', padding: 0,
        transition: 'background .2s',
      }}>
        <span style={{
          position: 'absolute', top: 3, left: value ? 21 : 3,
          width: 20, height: 20, borderRadius: '50%', background: '#fff',
          boxShadow: '0 1px 3px rgba(0,0,0,.3)',
          transition: 'left .18s',
        }}/>
      </button>
    </div>
  );
}

function MemberRow({ name, role, photo, add }) {
  if (add) {
    return (
      <div style={{
        display: 'flex', alignItems: 'center', gap: 10,
        padding: '14px 16px', cursor: 'pointer',
        color: 'var(--accent-a)',
      }}>
        <div style={{
          width: 34, height: 34, borderRadius: '50%',
          border: '1.5px dashed var(--accent-a)',
          display: 'flex', alignItems: 'center', justifyContent: 'center',
        }}>{I.plus(16)}</div>
        <div style={{ fontSize: 13.5, fontWeight: 500 }}>пригласить</div>
      </div>
    );
  }
  return (
    <div style={{
      display: 'flex', alignItems: 'center', gap: 10,
      padding: '12px 16px',
      borderBottom: '1px solid rgba(200,155,240,.10)',
    }}>
      <TgAvatar user={{ name, photo }} size={34} glow={false} />
      <div style={{ flex: 1 }}>
        <div style={{ fontSize: 13.5, fontWeight: 500 }}>{name}</div>
        <div style={{ fontSize: 11, color: 'var(--text-dim)', marginTop: 1 }}>{role}</div>
      </div>
      {I.chevRight(14)}
    </div>
  );
}

function SimpleRow({ label, sub, danger, last, onClick }) {
  return (
    <div onClick={onClick} style={{
      display: 'flex', alignItems: 'center', justifyContent: 'space-between',
      padding: '14px 16px',
      borderBottom: last ? 'none' : '1px solid rgba(200,155,240,.10)',
      cursor: 'pointer',
      color: danger ? '#ff9eb8' : 'var(--text)',
      fontSize: 13.5, fontWeight: 500,
    }}>
      <div style={{ flex: 1, minWidth: 0 }}>
        <div>{label}</div>
        {sub && <div style={{ fontSize: 11, color: 'var(--text-dim)', fontWeight: 400, marginTop: 2 }}>{sub}</div>}
      </div>
      {!danger && <span style={{ color: 'var(--text-dim)', marginLeft: 8 }}>{I.chevRight(14)}</span>}
    </div>
  );
}

// ─────────────────────────────────────────────────────────────────────────────
// State screens (Loading / Empty / Error)
// ─────────────────────────────────────────────────────────────────────────────
function CatalogSkeleton({ count = 6 }) {
  const shimmer = {
    background: 'linear-gradient(90deg, rgba(255,255,255,.04) 0%, rgba(200,155,240,.10) 50%, rgba(255,255,255,.04) 100%)',
    backgroundSize: '200% 100%',
    animation: 'nya-shimmer 1.4s linear infinite',
  };
  return (
    <div style={{ display: 'grid', gridTemplateColumns: 'repeat(2, minmax(0, 1fr))', gap: 10 }}>
      {Array.from({ length: count }, (_, i) => (
        <div key={i} style={{
          padding: 10, borderRadius: 'var(--radius)',
          background: 'var(--card-bg)', border: '1px solid var(--card-border)',
          height: 268, display: 'flex', flexDirection: 'column', gap: 10,
        }}>
          <div style={{ height: 150, borderRadius: 'var(--radius-sm)', ...shimmer }}/>
          <div style={{ height: 11, width: '60%', borderRadius: 6, ...shimmer }}/>
          <div style={{ height: 14, width: '80%', borderRadius: 6, ...shimmer }}/>
          <div style={{ height: 11, width: '40%', borderRadius: 6, ...shimmer }}/>
          <div style={{ flex: 1 }}/>
          <div style={{ height: 14, width: '50%', borderRadius: 6, ...shimmer }}/>
        </div>
      ))}
    </div>
  );
}

function EmptyState({ title, text, cta }) {
  return (
    <div style={{
      display: 'flex', flexDirection: 'column', alignItems: 'center',
      padding: '40px 24px', textAlign: 'center', gap: 14,
    }}>
      <GhostKitten size={120} variant="sleepy" />
      <div>
        <div style={{ fontFamily: 'Comfortaa, sans-serif', fontWeight: 600, fontSize: 18 }}>{title}</div>
        <div style={{ marginTop: 6, fontSize: 13, color: 'var(--text-dim)', maxWidth: 240, lineHeight: 1.4 }}>{text}</div>
      </div>
      {cta}
    </div>
  );
}

function LoadingScreen({ tweaks }) {
  return (
    <div style={{
      position: 'absolute', inset: 0,
      display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center',
      gap: 18, padding: 24, textAlign: 'center',
    }}>
      <DecorLayer density={tweaks.decor * 0.5} seed="load" palette={tweaks.palette} includeMoon />
      <div style={{ position: 'relative', zIndex: 2, display: 'flex', flexDirection: 'column', alignItems: 'center' }}>
        <div style={{ animation: 'nya-float 2.6s ease-in-out infinite' }}>
          <GhostKitten size={140} variant="sleepy" />
        </div>
        <div style={{ marginTop: 8, fontFamily: 'Comfortaa, sans-serif', fontWeight: 600, fontSize: 16, color: 'var(--text)' }}>
          собираю твои фигурки…
        </div>
        <div style={{
          marginTop: 14, display: 'flex', gap: 6, justifyContent: 'center',
        }}>
          {[0,1,2].map(i => (
            <span key={i} style={{
              width: 8, height: 8, borderRadius: '50%',
              background: 'var(--accent-a)',
              boxShadow: '0 0 8px var(--glow-strong)',
              animation: `nya-bounce 1.2s ease-in-out ${i * 0.15}s infinite`,
            }}/>
          ))}
        </div>
      </div>
    </div>
  );
}

function ErrorState({ onRetry, tweaks }) {
  return (
    <div style={{
      display: 'flex', flexDirection: 'column', alignItems: 'center',
      padding: '60px 24px', textAlign: 'center', gap: 14,
    }}>
      <GhostKitten size={120} variant="sad" />
      <div>
        <div style={{ fontFamily: 'Comfortaa, sans-serif', fontWeight: 600, fontSize: 18 }}>что-то пошло не так</div>
        <div style={{ marginTop: 6, fontSize: 13, color: 'var(--text-dim)', maxWidth: 240, lineHeight: 1.4 }}>
          котёнок не смог достать данные. попробуем ещё раз?
        </div>
      </div>
      <GradientButton onClick={onRetry}>повторить</GradientButton>
    </div>
  );
}

// ─────────────────────────────────────────────────────────────────────────────
// 7. Notifications
// ─────────────────────────────────────────────────────────────────────────────
const NYA_NOTIFICATIONS = [
  { id: 'n1', kind: 'pay',     figureId: 'f6', text: 'оплата через 2 дня',          time: 'сейчас',   unread: true },
  { id: 'n2', kind: 'price',   figureId: 'f9', text: 'цена снизилась на 12%',       time: '1 ч назад', unread: true },
  { id: 'n3', kind: 'release', figureId: 'f3', text: 'релиз через неделю',          time: 'сегодня',  unread: true },
  { id: 'n4', kind: 'ship',    figureId: 'f4', text: 'отправлена со склада',        time: 'вчера',    unread: false },
  { id: 'n5', kind: 'wishlist',figureId: 'f5', text: 'вернулась в наличие',         time: '2 дн',     unread: false },
  { id: 'n6', kind: 'pay',     figureId: 'f1', text: 'платёж принят, спасибо',      time: '3 дн',     unread: false },
  { id: 'n7', kind: 'release', figureId: 'f8', text: 'релиз перенесли на месяц',    time: '5 дн',     unread: false },
];

const NOTIF_ICONS = {
  pay:      (s = 20) => <svg width={s} height={s} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><rect x="3" y="6" width="18" height="13" rx="2.5"/><path d="M3 11 H 21"/><circle cx="17" cy="15.5" r="1.2" fill="currentColor"/></svg>,
  price:    (s = 20) => <svg width={s} height={s} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><path d="M3 17 L 9 11 L 13 14 L 21 6"/><path d="M21 11 V 6 H 16"/></svg>,
  release:  (s = 20) => <svg width={s} height={s} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><rect x="3" y="5" width="18" height="16" rx="3"/><path d="M3 10 H 21"/><path d="M8 3 V 7"/><path d="M16 3 V 7"/><circle cx="12" cy="15" r="2" fill="currentColor"/></svg>,
  ship:     (s = 20) => <svg width={s} height={s} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><path d="M3 8 H 14 V 16 H 3 Z"/><path d="M14 11 H 18 L 21 14 V 16 H 14"/><circle cx="7" cy="18" r="1.8"/><circle cx="17" cy="18" r="1.8"/></svg>,
  wishlist: (s = 20) => I.heart(s, 'currentColor'),
};

const NOTIF_COLORS = {
  pay:      'linear-gradient(135deg, #ffb3c9, #c89bf0)',
  price:    'linear-gradient(135deg, #9ecdff, #c89bf0)',
  release:  'linear-gradient(135deg, #f5e6a8, #ffb3c9)',
  ship:     'linear-gradient(135deg, #b3f0c9, #9ecdff)',
  wishlist: 'linear-gradient(135deg, #ff9ecd, #f5a3c7)',
};

function NotificationsScreen({ go, goBack, tweaks }) {
  const figures = useFigures();
  const [items, setItems] = React.useState(NYA_NOTIFICATIONS);
  const unread = items.filter(n => n.unread).length;
  const markAll = () => setItems(prev => prev.map(n => ({ ...n, unread: false })));

  // group by section
  const groups = React.useMemo(() => {
    const today = [], earlier = [];
    items.forEach(n => (['сейчас','сегодня','1 ч назад'].includes(n.time) ? today : earlier).push(n));
    return [
      { label: 'новые', items: today },
      { label: 'ранее', items: earlier },
    ].filter(g => g.items.length > 0);
  }, [items]);

  return (
    <div style={{ padding: '0 16px 100px', position: 'relative' }}>
      <DecorLayer density={tweaks.decor * 0.3} seed="notif" palette={tweaks.palette} />

      <div style={{ position: 'relative', zIndex: 2 }}>
        {/* Header */}
        <div style={{ display: 'flex', alignItems: 'center', gap: 10, padding: '8px 0 14px' }}>
          <IconChip onClick={goBack}>{I.back(20)}</IconChip>
          <div style={{ flex: 1 }}>
            <h1 style={{
              margin: 0, fontFamily: 'Comfortaa, sans-serif', fontWeight: 600,
              fontSize: 22, lineHeight: 1,
              background: 'linear-gradient(135deg, #fff 0%, var(--accent-a) 70%)',
              WebkitBackgroundClip: 'text', backgroundClip: 'text', color: 'transparent',
            }}>уведомления</h1>
            <div style={{ fontSize: 12, color: 'var(--text-dim)', marginTop: 2 }}>
              {unread > 0 ? `${unread} ${ruPlural(unread, ['новое','новых','новых'])}` : 'всё прочитано'}
            </div>
          </div>
          {unread > 0 && (
            <button onClick={markAll} style={{
              background: 'transparent', border: 0, padding: '6px 10px',
              color: 'var(--accent-a)', fontSize: 12, fontWeight: 600, cursor: 'pointer',
            }}>прочитать всё</button>
          )}
        </div>

        {items.length === 0 ? (
          <EmptyState
            title="всё спокойно"
            text="новых уведомлений пока нет"
          />
        ) : groups.map(g => (
          <React.Fragment key={g.label}>
            <SectionLabel>{g.label}</SectionLabel>
            <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
              {g.items.map(n => {
                const f = figures.find(x => x.id === n.figureId);
                const renderIcon = NOTIF_ICONS[n.kind] || NOTIF_ICONS.pay;
                return (
                  <GlassCard
                    key={n.id} padding={12} active={n.unread}
                    onClick={() => { f && go('detail', f); }}
                    style={{ display: 'flex', alignItems: 'center', gap: 12 }}
                  >
                    <div style={{
                      width: 42, height: 42, borderRadius: 14,
                      background: NOTIF_COLORS[n.kind] || NOTIF_COLORS.pay,
                      color: '#3a1e4d',
                      display: 'flex', alignItems: 'center', justifyContent: 'center',
                      flexShrink: 0,
                      boxShadow: '0 4px 12px rgba(0,0,0,.2)',
                    }}>
                      {renderIcon(20)}
                    </div>
                    <div style={{ flex: 1, minWidth: 0 }}>
                      <div style={{
                        fontFamily: 'Comfortaa, sans-serif', fontWeight: 600, fontSize: 13.5,
                        overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap',
                      }}>
                        {f ? f.name : 'NyaShelf'}
                      </div>
                      <div style={{
                        fontSize: 12, color: 'var(--text-dim)', marginTop: 2,
                        overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap',
                      }}>
                        {n.text}
                      </div>
                    </div>
                    <div style={{
                      display: 'flex', flexDirection: 'column', alignItems: 'flex-end', gap: 4,
                      flexShrink: 0,
                    }}>
                      <span style={{ fontSize: 10.5, color: 'var(--text-dim)' }}>{n.time}</span>
                      {n.unread && (
                        <span style={{
                          width: 8, height: 8, borderRadius: '50%',
                          background: 'var(--accent-a)',
                          boxShadow: '0 0 6px var(--glow-strong)',
                        }}/>
                      )}
                    </div>
                  </GlassCard>
                );
              })}
            </div>
          </React.Fragment>
        ))}
      </div>
    </div>
  );
}

Object.assign(window, {
  FigureCard, DashboardScreen, FigureDetailScreen, CalendarScreen,
  SearchScreen, WishlistScreen, SettingsScreen, NotificationsScreen,
  EmptyState, LoadingScreen, ErrorState, CatalogSkeleton,
  NOTIF_ICONS, NOTIF_COLORS,
});
