// THE EPIZODE — catalog page (Каталог).
// (rev: ВАРИАНТ 02 — фото 4/5, кампейн справа 2 ряда. ЗАФИКСИРОВАНО, не менять.)
// Grid of all ARTICLES (7 SKUs), grouped/filterable by episode.

function EpzCatalogPage() {
  const F = EPZ_FONTS;
  useEpzLang();
  const articles = epzHomeShowcase();
  const [theme, setTheme, T] = useEpzTheme();
  const store = useEpzStore();
  const [addToast, setAddToast] = React.useState(null);

  function handleWishToast(id) {
    if (store.wish.includes(id)) { store.toggleWish(id); epzWishSizeSet(id, null); return; }
    const a = epzHomeShowcase().find((x) => (x.wishId || x.id) === id) || epzArticle(id);
    epzWishAdd(a || { id });
  }

  // Quick-add from catalog: ONE SIZE / no-choice items drop straight into cart;
  // anything needing a size or variant opens its product page to choose.
  function quickAdd(a, size) {
    const cs = size || (a.sizes && a.sizes.length === 1 ? a.sizes[0] : null);
    if (!cs) return;
    // Bundle of two garments — add each part (picked size applies to the part without its own).
    if (a.isBundle && a.bundleParts) {
      a.bundleParts.forEach((p) => {
        store.addToCart(p.id, { variant: p.variant || null, size: p.size || cs, qty: 1 });
      });
      setAddToast(`${a.short || a.title} · ${cs} — в корзине`);
      setTimeout(() => setAddToast(null), 2600);
      return;
    }
    // Pyjama "set"/variant — add with its variant.
    if (a.cartVariant) {
      store.addToCart(a.cartId || a.id, { variant: a.cartVariant, size: cs, qty: 1 });
      setAddToast(`${a.short || a.title} · ${cs} — в корзине`);
      setTimeout(() => setAddToast(null), 2600);
      return;
    }
    store.addToCart(a.cartId || a.id, { variant: null, size: cs, qty: 1 });
    setAddToast(`${a.short || a.title} · ${cs} — в корзине`);
    setTimeout(() => setAddToast(null), 2600);
  }

  // Filters
  const [episodeFilter, setEpisodeFilter] = React.useState('all');
  const [fabricFilter, setFabricFilter] = React.useState('all');
  const [catFilter, setCatFilter] = React.useState('all');
  const [sort, setSort] = React.useState('price-desc');
  const [view, setView] = React.useState('grid'); // grid | list
  const gridRef = React.useRef(null);
  const filterBarRef = React.useRef(null);

  // Фильтр-бар статичный — не преследует при скролле (по просьбе).

  let filtered = articles.slice();
  if (episodeFilter !== 'all') filtered = filtered.filter((a) => a.episode === episodeFilter);
  if (fabricFilter !== 'all') filtered = filtered.filter((a) => a.fabric.toLowerCase().includes(fabricFilter));
  if (catFilter !== 'all') filtered = filtered.filter((a) => catCategory(a) === catFilter);

  filtered.sort((a, b) => {
    const pa = epzArticleFrom(a), pb = epzArticleFrom(b);
    if (sort === 'price-asc')  return pa - pb;
    if (sort === 'price-desc') return pb - pa;
    return 0;
  });

  const episodeChips = [['all', 'все эпизоды'], ...EPZ_EPISODES.map((e) => [e.id, epzT(e.title)])];
  const fabricChips = [
    ['all', 'все ткани'],
    ['шёлк', 'шёлк'],
    ['шерсть', 'шерсть'],
    ['вискоза', 'вискоза'],
  ];
  const catChips = [
    ['all', 'всё'],
    ['комплекты', 'комплекты'],
    ['платья', 'платья'],
    ['верх', 'топы'],
    ['низ', 'брюки'],
    ['тренч', 'тренч'],
    ['аксессуары', 'аксессуары'],
  ];

  // Per-row title equalization: titles in each row of 3 get the same height so
  // descriptions below them line up. Robust replacement for CSS subgrid.
  React.useLayoutEffect(() => {
    const grid = gridRef.current;
    if (!grid) return;
    const equalize = () => {
      const cols = getComputedStyle(grid).gridTemplateColumns.split(' ').length || 3;
      // Equalize both titles and descriptions per row so the divider line above
      // «ткань» sits at the same level across each row, with no extra air.
      ['.cat-meta'].forEach((sel) => {
        const els = [...grid.querySelectorAll(sel)];
        els.forEach((t) => { t.style.minHeight = ''; });
        for (let i = 0; i < els.length; i += cols) {
          const row = els.slice(i, i + cols);
          const max = Math.max(...row.map((t) => t.offsetHeight));
          row.forEach((t) => { t.style.minHeight = max + 'px'; });
        }
      });
    };
    equalize();
    const t = setTimeout(equalize, 250);
    if (document.fonts && document.fonts.ready) document.fonts.ready.then(equalize);
    window.addEventListener('resize', equalize);
    return () => { clearTimeout(t); window.removeEventListener('resize', equalize); };
  }, [filtered.length, view, episodeFilter, fabricFilter, catFilter, theme]);

  return (
    <div style={{
      width: '100%',
      background: T.bg, color: T.text,
      fontFamily: F.mono, fontSize: 13, lineHeight: 1.5,
      WebkitFontSmoothing: 'antialiased',
      transition: 'background 0.3s ease, color 0.3s ease',
    }}>
      <style>{`
        .cat-chip { transition: background 0.2s, color 0.2s, border-color 0.2s, opacity 0.2s; cursor: pointer; }
        .cat-card:hover .cat-arrow { transform: translateX(4px); opacity: 1; }
        .cat-arrow { transition: all 0.25s ease; opacity: 0.5; }
        .cat-hover-sizes { opacity: 0; transition: opacity 0.28s ease; pointer-events: none; }
        .cat-card:hover .cat-hover-sizes { opacity: 1; pointer-events: auto; }
        .cat-card:hover .cat-content-main { opacity: 0; }
        .cat-content-main { transition: opacity 0.28s ease; }
        @media (hover: none) { .cat-hover-sizes { display: none !important; } }
        @keyframes catSlideIn { from { opacity: 0; transform: translateY(12px); } to { opacity: 1; transform: translateY(0); } }
        .cat-card { animation: catSlideIn 0.35s cubic-bezier(.2,.7,.2,1) both; }
        .cat-card:hover .cat-img-hover { opacity: 1 !important; }
        .cat-card:hover .cat-img-base { opacity: 0; }
        @keyframes epzRecBlink { 0%, 55%, 100% { opacity: 1; } 65%, 90% { opacity: 0.15; } }
        .epz-rec-dot { animation: epzRecBlink 1.8s ease-in-out infinite; }
        .cat-grid { grid-template-columns: repeat(4, 1fr); }
        @media (max-width: 700px) {
          .cat-grid { grid-template-columns: repeat(2, 1fr); }
          .cat-bigphoto { grid-column: 1 / -1 !important; grid-row: auto !important; aspect-ratio: 16 / 9; }
        }
        .cat-filterstrip { scrollbar-width: none; -ms-overflow-style: none; }
        .cat-filterstrip::-webkit-scrollbar { display: none; }
      `}</style>

      <EpzHeader theme={theme} setTheme={setTheme} T={T} current="episodes" />

      {/* breadcrumb */}
      <EpzCrumbs T={T} items={[
        { label: epzT('главная'), href: '/' },
        { label: epzT('каталог') },
      ]} />

      {/* ═════════════ FILTER BAR ═════════════ */}
      <section ref={filterBarRef} style={{
        background: T.bg,
        borderTop: `1px solid ${T.border}`,
        borderBottom: `1px solid ${T.border}`,
        padding: '11px 32px',
        display: 'grid', gridTemplateColumns: '1fr auto', gap: 20, alignItems: 'center',
        transition: 'background 0.3s ease, border-color 0.3s ease',
      }}>
        <div className="cat-filterstrip" style={{ display: 'flex', flexDirection: 'column', gap: 7, minWidth: 0 }}>
          <ChipGroup label="категория" value={catFilter} setValue={setCatFilter} options={catChips} T={T} />
          <ChipGroup label="эпизод" value={episodeFilter} setValue={setEpisodeFilter} options={episodeChips} T={T} />
          <ChipGroup label="ткань" value={fabricFilter} setValue={setFabricFilter} options={fabricChips} T={T} />
        </div>

        <div style={{ display: 'flex', justifyContent: 'flex-end', gap: 14, alignItems: 'center', flexShrink: 0 }}>
          <select value={sort} onChange={(e) => setSort(e.target.value)} style={{
            background: 'transparent', color: T.text, border: `1px solid ${T.border}`, borderRadius: 0,
            padding: '5px 7px', fontFamily: F.mono, fontSize: 9, letterSpacing: '0.12em', textTransform: 'uppercase',
            cursor: 'pointer', outline: 'none',
          }}>
            <option value="price-desc">{epzT('цена')} ↓</option>
            <option value="price-asc">{epzT('цена')} ↑</option>
          </select>
          <div style={{ display: 'flex', border: `1px solid ${T.border}` }}>
            {[['grid', 'сетка'], ['list', 'список']].map(([v, lbl]) => (
              <button key={v} onClick={() => setView(v)} style={{
                padding: '5px 9px', fontFamily: F.mono, fontSize: 9, letterSpacing: '0.16em',
                textTransform: 'uppercase', cursor: 'pointer',
                background: view === v ? T.accent : 'transparent',
                color: view === v ? T.accentText : T.text, border: 0,
              }}>{epzT(lbl)}</button>
            ))}
          </div>
        </div>
      </section>

      {/* ═════════════ RESULTS COUNT ═════════════ */}
      <div style={{
        padding: '14px 32px',
        display: 'flex', justifyContent: 'space-between',
        fontSize: 10, letterSpacing: '0.22em', textTransform: 'uppercase', opacity: 0.6,
        borderBottom: `1px solid ${T.border}`,
      }}>
        <span>{epzT('результат')} · {filtered.length} {epzIsEN() ? (filtered.length === 1 ? 'item' : 'items') : plural(filtered.length, 'изделие', 'изделия', 'изделий')}</span>
        {(episodeFilter !== 'all' || fabricFilter !== 'all' || catFilter !== 'all') && (
          <button onClick={() => { setEpisodeFilter('all'); setFabricFilter('all'); setCatFilter('all'); }} style={{
            background: 'transparent', color: 'inherit', border: 0, padding: 0,
            fontFamily: F.mono, fontSize: 10, letterSpacing: '0.22em', textTransform: 'uppercase',
            cursor: 'pointer', textDecoration: 'underline', textUnderlineOffset: 3,
          }}>{epzT('сбросить фильтры')} ×</button>
        )}
      </div>

      {/* ═════════════ GRID / LIST ═════════════ */}
      {filtered.length === 0 ? (
        <section style={{ padding: '90px 32px 80px', textAlign: 'center' }}>
          <div style={{ fontFamily: F.display, fontSize: 48, fontStyle: 'italic', fontWeight: 300, opacity: 0.85 }}>
            {epzT('Упс, по этим фильтрам ничего нет')}
          </div>
          <button onClick={() => { setEpisodeFilter('all'); setFabricFilter('all'); setCatFilter('all'); }} style={{
            marginTop: 18, background: 'transparent', color: 'inherit', border: 0,
            fontFamily: F.mono, fontSize: 12, letterSpacing: '0.18em', textTransform: 'uppercase',
            cursor: 'pointer', textDecoration: 'underline', textUnderlineOffset: 3,
          }}>{epzT('сбросить')} ×</button>
          {(() => {
            // Ближайшие к выбранным фильтрам: считаем совпадения по каждому активному фильтру
            const score = (a) => {
              let s = 0;
              if (episodeFilter !== 'all' && a.episode === episodeFilter) s += 1;
              if (fabricFilter !== 'all' && a.fabric.toLowerCase().includes(fabricFilter)) s += 1;
              if (catFilter !== 'all' && catCategory(a) === catFilter) s += 1;
              return s;
            };
            const near = articles.slice().map((a) => [a, score(a)]).filter(([, s]) => s > 0)
              .sort((x, y) => y[1] - x[1]).map(([a]) => a);
            if (!near.length) return null;
            return (
              <div style={{ marginTop: 70, textAlign: 'left' }}>
                <div style={{ fontFamily: F.display, fontSize: 30, fontStyle: 'italic', fontWeight: 300, textAlign: 'center', marginBottom: 26 }}>{epzT('Вам может понравиться')}</div>
                <div className="cat-grid" style={{ display: 'grid', gap: 9, maxWidth: near.length < 4 ? near.length * 330 : 'none', margin: '0 auto' }}>
                  {near.map((a) => (
                    <CatalogCard key={a.id} a={a} T={T} variant="grid"
                      position={{ first: false }}
                      wished={store.wish.includes(a.id)} onWish={handleWishToast} onAdd={quickAdd} />
                  ))}
                </div>
              </div>
            );
          })()}
        </section>
      ) : view === 'grid' ? (
        <section style={{ padding: '11px 32px 80px' }}>
          <div ref={gridRef} className="cat-grid" style={{
            display: 'grid', gridAutoRows: 'auto',
            gridAutoFlow: 'dense', gap: '9px',
          }}>
            {(() => {
              const isFull = filtered.length === articles.length; // big photos only on the unfiltered view
              const bigPhoto = (key, href, ep, label, colRow) => (
                <a key={key} href={href} className="cat-bigphoto" style={{
                  gridColumn: colRow.col, gridRow: colRow.row, alignSelf: 'stretch', position: 'relative', display: 'block',
                  textDecoration: 'none', color: 'inherit', background: T.bg, overflow: 'hidden',
                }}>
                  <div style={{
                    position: 'absolute', inset: 0,
                    background: `repeating-linear-gradient(135deg, ${T.placeholderTone === 'dark' ? '#241015' : '#E3D9C4'} 0 16px, ${T.placeholderTone === 'dark' ? '#2C1318' : '#DCD0B8'} 16px 17px)`,
                    display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', gap: 14, textAlign: 'center', padding: 24,
                  }}>
                    <div style={{ fontFamily: F.mono, fontSize: 10, letterSpacing: '0.24em', textTransform: 'uppercase', opacity: 0.55 }}>[ {epzT('кампейн-фото')} ]</div>
                    <div style={{ fontFamily: F.display, fontSize: 'clamp(34px, 3.4vw, 60px)', fontStyle: 'italic', fontWeight: 300, lineHeight: 1.02 }}>{epzT(ep)}<br />{epzT(label)}</div>
                    <div style={{ fontFamily: F.mono, fontSize: 10, letterSpacing: '0.22em', textTransform: 'uppercase', color: T.accentInk, opacity: 0.85 }}>{epzT('смотреть эпизод →')}</div>
                  </div>
                </a>
              );
              const els = [];
              if (isFull) {
                // BIG 1 — right, rows 1–2.  BIG 2 — left, rows 4–5.
                els.push(bigPhoto('big-1', '/product/?id=002-1', epzT('Эпизод') + ' 002', epzT('Эльвира'), { col: '3 / span 2', row: '1 / span 2' }));
                els.push(bigPhoto('big-2', '/product/?id=001-set', epzT('Эпизод') + ' 001', epzT('Катрин'), { col: '1 / span 2', row: '4 / span 2' }));
              }
              filtered.forEach((a, i) => {
                els.push(
                  <CatalogCard key={a.id} a={a} T={T} variant="grid"
                    position={{ first: false }}
                    wished={store.wish.includes(a.id)} onWish={handleWishToast} onAdd={quickAdd} />
                );
              });
              return els;
            })()}
          </div>
        </section>
      ) : (
        <section style={{ padding: '20px 32px 80px' }}>
          {filtered.map((a) => <CatalogCard key={a.id} a={a} T={T} variant="list"
            wished={store.wish.includes(a.id)} onWish={handleWishToast} onAdd={quickAdd} />)}
        </section>
      )}

      <EpzFooter T={T} />
    </div>
  );
}

function plural(n, one, few, many) {
  const m10 = n % 10, m100 = n % 100;
  if (m10 === 1 && m100 !== 11) return one;
  if (m10 >= 2 && m10 <= 4 && (m100 < 10 || m100 >= 20)) return few;
  return many;
}

// ─── Category of a showcase card (комплекты first, then платья, ...) ─────────
function catCategory(a) {
  if (a.bundleSave || a.isBundle) return 'комплекты';
  if (a.type === 'Тренч') return 'тренч';
  if (a.type === 'Платок') return 'аксессуары';
  if (/^Топ/.test(a.title)) return 'верх';
  if (/^Брюки/.test(a.title)) return 'низ';
  if ((a.type || '').toLowerCase().includes('платье')) return 'платья';
  return 'другое';
}

// ─── Filter chip group ───────────────────────────────────────────────────────
function ChipGroup({ label, value, setValue, options, T }) {
  const F = EPZ_FONTS;
  return (
    <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
      <span style={{ fontSize: 8, letterSpacing: '0.18em', textTransform: 'uppercase', opacity: 0.5, whiteSpace: 'nowrap', minWidth: 64 }}>{epzT(label)}</span>
      <div style={{ display: 'flex', gap: 4, flexWrap: 'wrap' }}>
        {options.map(([key, lbl]) => {
          const active = value === key;
          return (
            <button key={key} className="cat-chip" onClick={() => setValue(key)} style={{
              background: active ? T.accent : 'transparent',
              color: active ? T.accentText : T.text,
              border: `1px solid ${active ? T.accent : T.border}`,
              padding: '3px 8px', whiteSpace: 'nowrap',
              fontFamily: F.mono, fontSize: 8.5, letterSpacing: '0.12em', textTransform: 'uppercase',
              opacity: active ? 1 : 0.8,
            }}>{epzT(lbl)}</button>
          );
        })}
      </div>
    </div>
  );
}

// ─── Catalog card (article) ──────────────────────────────────────────────────
function CatalogCard({ a, T, variant, position = {}, wished = false, onWish, onAdd, hideEpisode = false }) {
  const F = EPZ_FONTS;
  const ep = epzEpisode(a.episode);
  const img = epzArticleImage(a);
  const href = a.href || `/product/?id=${a.id}`;
  const priceLabel = a.priceLabel || epzArticlePriceLabel(a);
  const [infoOpen, setInfoOpen] = React.useState(false);
  const [sizeOpen, setSizeOpen] = React.useState(false);
  const sizes = a.sizes || ['ONE SIZE'];
  const oneSize = sizes.length === 1;
  // Пижамные комплекты: размер топа и брюк выбирается отдельно
  const setParts = React.useMemo(() => {
    if (!a.bundleParts) return null;
    const parts = a.bundleParts.map((p) => {
      const pa = (typeof EPZ_ARTICLES !== 'undefined' && EPZ_ARTICLES.find((x) => x.id === p.id)) || {};
      const label = p.variant === 'top' ? 'топ' : p.variant === 'bottom' ? 'брюки' : (pa.type || 'вещь').toLowerCase();
      const psizes = p.size ? [p.size] : pa.sizes || ['ONE SIZE'];
      return { ...p, label, psizes, fixed: psizes.length === 1 };
    });
    return parts.filter((p) => !p.fixed).length > 1 ? parts : null;
  }, [a]);
  const [selParts, setSelParts] = React.useState({});
  function pickPartSize(e, i, s) {
    e.preventDefault(); e.stopPropagation();
    const next = { ...selParts, [i]: s };
    setSelParts(next);
    if (setParts.every((p, k) => p.fixed || next[k])) {
      setParts.forEach((p, k) => epzAddToCart(p.id, { variant: p.variant || null, size: p.fixed ? p.psizes[0] : next[k], qty: 1 }));
      try { epzToast(`${epzT(a.short || a.title)} — ${epzT('в корзине')}`); } catch (err) {}
      setTimeout(() => setSelParts({}), 1600);
      setSizeOpen(false);
    }
  }
  const partSizeRows = (align) => (
    <div style={{ display: 'flex', alignItems: 'center', justifyContent: align === 'flex-end' ? 'flex-end' : 'center', gap: 10 }}>
      {setParts.filter((p) => !p.fixed).map((p) => {
        const i = setParts.indexOf(p);
        return (
          <div key={i} style={{ display: 'flex', alignItems: 'center', gap: 4 }}>
            <span style={{ fontFamily: F.mono, fontSize: 8, letterSpacing: '0.16em', textTransform: 'uppercase', opacity: 0.55, marginRight: 2 }}>{epzT(p.label)}</span>
            {p.psizes.map((s) => (
              <button key={s} onClick={(e) => pickPartSize(e, i, s)} style={{
                padding: '7px 6px', cursor: 'pointer', lineHeight: 1,
                background: selParts[i] === s ? T.accent : 'transparent',
                color: selParts[i] === s ? T.accentText : T.text,
                border: `1px solid ${selParts[i] === s ? T.accent : T.text}`,
                fontFamily: F.mono, fontSize: 8.5, letterSpacing: '0.05em', textTransform: 'uppercase',
              }}>{s}</button>
            ))}
          </div>
        );
      })}
    </div>
  );

  function handleCart(e) {
    e.preventDefault(); e.stopPropagation();
    // bundle / variant → product page; one-size → add now; else open size popover
    if (a.isBundle || a.cartVariant) { window.location.href = a.href || `/product/?id=${a.id}`; return; }
    if (oneSize) { onAdd && onAdd(a, sizes[0]); return; }
    setSizeOpen((v) => !v);
  }
  function pickSize(e, s) {
    e.preventDefault(); e.stopPropagation();
    onAdd && onAdd(a, s);
    setSizeOpen(false);
  }
  const cartBtn = (compact) => (
    <span style={{ position: 'relative', display: 'inline-flex' }}>
      <button onClick={handleCart} aria-label="В корзину" style={{
        flexShrink: 0, width: compact ? 44 : 46, height: compact ? 44 : 'auto', padding: compact ? 0 : '12px', cursor: 'pointer',
        background: a.isBundle || a.cartVariant ? 'transparent' : T.accent,
        color: a.isBundle || a.cartVariant ? T.accent : T.accentText,
        border: a.isBundle || a.cartVariant ? `1px solid ${T.accent}` : 0,
        display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
      }}>
        <EpzIcon name="bag" size={17} />
      </button>
      {sizeOpen && !oneSize && (
        <span onClick={(e) => { e.preventDefault(); e.stopPropagation(); }} style={{
          position: 'absolute', bottom: '120%', right: 0, zIndex: 25,
          background: T.bg, color: T.text, border: `1px solid ${T.accent}`,
          boxShadow: '0 16px 44px rgba(0,0,0,0.22)', padding: 10, display: 'flex', gap: 6, whiteSpace: 'nowrap',
        }}>
          {sizes.map((s) => (
            <button key={s} onClick={(e) => pickSize(e, s)} style={{
              minWidth: 38, padding: '9px 8px', cursor: 'pointer', background: 'transparent', color: T.text,
              border: `1px solid ${T.border}`, fontFamily: F.mono, fontSize: 11, letterSpacing: '0.1em', textTransform: 'uppercase',
            }}>{s}</button>
          ))}
          <span style={{ position: 'absolute', top: '100%', right: 16, width: 9, height: 9, background: T.bg, borderRight: `1px solid ${T.accent}`, borderBottom: `1px solid ${T.accent}`, transform: 'translateY(-50%) rotate(45deg)' }} />
        </span>
      )}
    </span>
  );

  function toggleInfo(e) {
    e.preventDefault(); e.stopPropagation();
    setInfoOpen((v) => !v);
  }
  // Popover explaining the bundle saving.
  const bundleInfo = a.bundleSave ? (
    <span style={{ position: 'relative', display: 'inline-flex' }}>
      <button onClick={toggleInfo} aria-label="Подробнее о комплекте" style={{
        width: 16, height: 16, borderRadius: '50%', border: `1px solid ${T.accent}`,
        color: T.accentInk, background: 'transparent', cursor: 'pointer', flexShrink: 0,
        fontFamily: F.mono, fontSize: 9, lineHeight: 1, display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
      }}>i</button>
      {infoOpen && (
        <span onClick={(e) => { e.preventDefault(); e.stopPropagation(); }} style={{
          position: 'absolute', bottom: '140%', right: -8, zIndex: 20, width: 244,
          background: T.bg, color: T.text, border: `1px solid ${T.accent}`,
          boxShadow: '0 18px 50px rgba(0,0,0,0.25)', padding: '14px 16px', textAlign: 'left',
        }}>
          <span style={{ fontFamily: F.mono, fontSize: 9, letterSpacing: '0.16em', textTransform: 'uppercase', color: T.accentInk, display: 'block', marginBottom: 10 }}>{epzT('чек комплекта')}</span>
          <span style={{ display: 'block', fontFamily: F.mono, fontSize: 11, letterSpacing: '0.02em', lineHeight: 1.9 }}>
            {(a.bundleItems || []).map(([lbl, p]) => (
              <span key={lbl} style={{ display: 'flex', justifyContent: 'space-between', gap: 12 }}>
                <span style={{ textTransform: 'uppercase', opacity: 0.85 }}>{lbl}</span><span>{p.toLocaleString('ru-RU')}</span>
              </span>
            ))}
            <span style={{ display: 'block', borderTop: `1px solid ${T.border}`, margin: '6px 0' }} />
            <span style={{ display: 'flex', justifyContent: 'space-between', gap: 12, opacity: 0.6 }}>
              <span style={{ textTransform: 'uppercase' }}>итого порознь</span><span>{a.bundleFull.toLocaleString('ru-RU')}</span>
            </span>
            <span style={{ display: 'flex', justifyContent: 'space-between', gap: 12, color: T.accentInk }}>
              <span style={{ textTransform: 'uppercase' }}>комплектом</span><span>{a.price.toLocaleString('ru-RU')}</span>
            </span>
          </span>
          <span style={{ position: 'absolute', top: '100%', right: 12, width: 10, height: 10, background: T.bg, borderRight: `1px solid ${T.accent}`, borderBottom: `1px solid ${T.accent}`, transform: 'translateY(-50%) rotate(45deg)' }} />
        </span>
      )}
    </span>
  ) : null;

  function handleWish(e) {
    e.preventDefault(); e.stopPropagation();
    if (onWish) onWish(a.id);
  }
  const heart = (
    <button onClick={handleWish} aria-label={wished ? epzT('Убрать из избранного') : epzT('В избранное')} style={{
      position: 'absolute', top: 12, right: 12, zIndex: 2,
      display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
      background: 'transparent', border: 0, cursor: 'pointer', padding: 0,
      color: EPZ_COLORS.burgundy, filter: 'drop-shadow(0 1px 2px rgba(255,255,255,0.5))',
    }}>
      <EpzIcon name={wished ? 'heart-filled' : 'heart'} size={19} />
    </button>
  );

  if (variant === 'list') {
    return (
      <a href={href} className="cat-card" style={{
        display: 'grid', gridTemplateColumns: '120px 1fr auto auto', gap: 28, alignItems: 'center',
        padding: '24px 0', borderBottom: `1px solid ${T.border}`,
        color: 'inherit', textDecoration: 'none',
      }}>
        <div style={{ position: 'relative' }}>
          {img ? (
            <div style={{ aspectRatio: '3/4', backgroundColor: '#ffffff', backgroundImage: `url(${img})`, backgroundSize: 'contain', backgroundRepeat: 'no-repeat', backgroundPosition: 'center' }} />
          ) : (
            <EpzPlaceholder label={epzT(a.title)} sub={epzT(ep.title)} ratio="3/4" tone={T.placeholderTone} />
          )}
          {heart}
        </div>
        <div>
          <div style={{ fontFamily: F.display, fontSize: 34, lineHeight: 1.05 }}>{epzT(a.title)}</div>
          <div style={{ marginTop: 6, fontSize: 13, opacity: 0.7, fontFamily: F.body, letterSpacing: 0, textTransform: 'none' }}>
            {a.bundleSave ? `${epzT('Полный образ')} · ${epzT(a.fabric)}` : epzT(a.fabric)}
          </div>
        </div>
        <div style={{ fontSize: 10, letterSpacing: '0.18em', textTransform: 'uppercase', opacity: 0.7, textAlign: 'right' }}>
          <div style={{ opacity: 0.55, marginBottom: 8 }}>{epzT('размер · в корзину')}</div>
          {setParts ? partSizeRows('flex-end') : (
          <div style={{ display: 'flex', gap: 6, justifyContent: 'flex-end', flexWrap: 'wrap' }}>
            {(a.sizes || ['ONE SIZE']).map((s) => (
              <button key={s} onClick={(e) => pickSize(e, s)} style={{
                minWidth: 44, padding: '9px 10px', cursor: 'pointer', background: 'transparent', color: T.text,
                border: `1px solid ${T.text}`, fontFamily: F.mono, fontSize: 10, letterSpacing: '0.08em', textTransform: 'uppercase',
                transition: 'background 0.18s, color 0.18s',
              }}
                onMouseEnter={(e) => { e.currentTarget.style.background = T.accent; e.currentTarget.style.color = T.accentText; e.currentTarget.style.borderColor = T.accent; }}
                onMouseLeave={(e) => { e.currentTarget.style.background = 'transparent'; e.currentTarget.style.color = T.text; e.currentTarget.style.borderColor = T.text; }}
              >{s}</button>
            ))}
          </div>
          )}
        </div>
        <div style={{ textAlign: 'right' }}>
          {a.bundleSave ? (
            <>
              <div style={{ fontFamily: F.display, fontSize: 28, lineHeight: 1.1, color: T.accentInk }}>{priceLabel}</div>
            </>
          ) : (
            <>
              <div style={{ fontFamily: F.display, fontSize: 28, lineHeight: 1 }}>{priceLabel}</div>
            </>
          )}
          <div className="cat-arrow" style={{ marginTop: 12, display: 'flex', alignItems: 'center', justifyContent: 'flex-end', gap: 14 }}>
            <span style={{ fontFamily: F.mono, fontSize: 11, letterSpacing: '0.22em', textTransform: 'uppercase', color: T.accentInk }}>{epzT('Подробнее')} →</span>
          </div>
        </div>
      </a>
    );
  }

  // Hover sizes layer — replaces the name/price block on hover (sits over content area).
  // Always just size buttons (ONE SIZE shows as a single pill).
  const hoverSizes = (
    <div className="cat-hover-sizes" style={{
      position: 'absolute', inset: 0, zIndex: 3,
      display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'flex-start', gap: 9,
      padding: '16px 12px 12px', background: T.bg,
    }}>
      <div style={{ fontFamily: F.mono, fontSize: 8, letterSpacing: '0.2em', textTransform: 'uppercase', opacity: 0.55 }}>{epzT('выберите размер')}</div>
      {setParts ? partSizeRows() : (
      <div style={{ display: 'flex', gap: 7, justifyContent: 'center', flexWrap: 'wrap' }}>
        {sizes.map((s) => (
          <button key={s} onClick={(e) => pickSize(e, s)} style={{
            minWidth: 46, padding: '9px 9px', cursor: 'pointer', background: 'transparent', color: T.text,
            border: `1px solid ${T.text}`, fontFamily: F.mono, fontSize: 10, letterSpacing: '0.08em', textTransform: 'uppercase',
          }}>{s}</button>
        ))}
      </div>
      )}
    </div>
  );

  // Grid
  return (
    <a href={href} className="cat-card" style={{
      display: 'flex', flexDirection: 'column', height: '100%', textDecoration: 'none', color: 'inherit',
      background: T.bg,
    }}>
      {/* image — если задан a.catalogCover (кампейн-фото), обложка = кампейн (cover), обтравка показывается при наведении */}
      {img ? (
        a.catalogCover ? (
          <div className="cat-img is-photo" style={{ aspectRatio: '4/5', backgroundColor: '#241a18', position: 'relative', overflow: 'hidden' }}>
            <div className="cat-img-base" style={{ position: 'absolute', inset: 0, backgroundImage: `url(${a.catalogCover})`, backgroundSize: 'cover', backgroundRepeat: 'no-repeat', backgroundPosition: 'center 18%', transition: 'opacity 0.4s ease' }} />
            <div className="cat-img-hover" style={{ position: 'absolute', inset: 0, backgroundColor: EPZ_COLORS.photoBg, opacity: 0, transition: 'opacity 0.4s ease' }}>
              <div style={{ position: 'absolute', inset: 0, backgroundImage: `url(${img})`, backgroundSize: 'contain', backgroundRepeat: 'no-repeat', backgroundPosition: 'center', mixBlendMode: 'multiply' }} />
            </div>
            {heart}
          </div>
        ) : (
        <div className="cat-img is-photo" style={{ aspectRatio: '4/5', backgroundColor: EPZ_COLORS.photoBg, position: 'relative' }}>
          <div className="cat-img-base" style={{ position: 'absolute', inset: 0, backgroundImage: `url(${img})`, backgroundSize: 'contain', backgroundRepeat: 'no-repeat', backgroundPosition: 'center', mixBlendMode: 'multiply', transition: 'opacity 0.4s ease' }} />
          {a.hoverImage && (
            <div className="cat-img-hover" style={{ position: 'absolute', inset: 0, backgroundImage: `url(${a.hoverImage})`, backgroundSize: a.hoverCover ? 'cover' : 'contain', backgroundRepeat: 'no-repeat', backgroundPosition: a.hoverCover ? 'center 18%' : 'center', mixBlendMode: a.hoverCover ? 'normal' : 'multiply', opacity: 0, transition: 'opacity 0.4s ease' }} />
          )}
          {heart}
        </div>
        )
      ) : (
        <div style={{ position: 'relative' }}>
          {heart}
          <EpzPlaceholder label={epzT(a.title)} sub={epzT(ep.title)} ratio="3/4" tone={T.placeholderTone} />
        </div>
      )}

      {/* content — вариант 02: название·цена в строку, под ними тонкие метки ткань·предзаказ */}
      <div style={{ padding: '9px 11px 11px', display: 'flex', flexDirection: 'column', flex: 1, position: 'relative' }}>
        <div className="cat-content-main" style={{ display: 'flex', flexDirection: 'column', flex: 1 }}>
        <div style={{ flex: 1 }} />
        <div className="cat-meta">
          <div className="cat-title" style={{ fontFamily: F.mono, fontSize: 10, letterSpacing: '0.16em', textTransform: 'uppercase', lineHeight: 1.6, opacity: 0.85 }}>{epzT(a.title)}</div>
          <div style={{ marginTop: 3, fontFamily: F.mono, fontSize: 11, letterSpacing: '0.08em', lineHeight: 1.3 }}>
            {priceLabel}
          </div>
          {a.preorder &&
          <div style={{ marginTop: 7, fontFamily: F.mono, fontSize: 7.5, letterSpacing: '0.18em', textTransform: 'uppercase', color: T.accentInk }}>{epzT('предзаказ')}</div>
          }
        </div>
        </div>
        {hoverSizes}
      </div>
    </a>
  );
}

window.EpzCatalogPage = EpzCatalogPage;
