// Shared product card + catalog primitives

const { useState, useMemo } = React;

// 画像の優先順位:
//  1) p.images が配列で1枚以上 → そのまま使用(WP REST 経由はこちら)
//  2) p.imageUrl があれば1枚としてラップ
//  3) 静的フォルダ uploads/products/product_NN.jpg を1枚
// 重複URL(アイキャッチとサブ画像が同一など)はギャラリーの「進まない」原因になるため除外
function resolveProductImages(p) {
  let list;
  if (Array.isArray(p.images) && p.images.length > 0) list = p.images.filter(Boolean);
  else if (p.imageUrl) list = [p.imageUrl];
  else { const idStr = String(p.id).padStart(2, "0"); list = [`uploads/products/product_${idStr}.jpg`]; }
  return [...new Set(list)];
}

function ProductImage({ p, height = 220 }) {
  const images = React.useMemo(() => resolveProductImages(p), [p]);

  const [idx, setIdx] = React.useState(0);
  const [hovering, setHovering] = React.useState(false);
  const [okImages, setOkImages] = React.useState(images);

  // ホバー中は自動切替(1.2 秒ごと)
  React.useEffect(() => {
    if (!hovering || okImages.length < 2) return;
    const t = setInterval(() => setIdx((i) => (i + 1) % okImages.length), 1200);
    return () => clearInterval(t);
  }, [hovering, okImages.length]);

  // ホバーが外れたら 1 枚目に戻す
  React.useEffect(() => { if (!hovering) setIdx(0); }, [hovering]);

  const onImgError = (failedUrl) => {
    setOkImages((arr) => {
      const next = arr.filter((u) => u !== failedUrl);
      return next.length > 0 ? next : arr;
    });
    setIdx(0);
  };

  const showCount = okImages.length;
  const current = okImages[idx] || okImages[0];

  return (
    <div
      onMouseEnter={() => setHovering(true)}
      onMouseLeave={() => setHovering(false)}
      style={{
        height, background: "#f4f0e8",
        borderBottom: "1px solid #eae7e2", position: "relative", overflow: "hidden",
      }}
    >
      <img
        src={current}
        alt={p.name}
        loading="lazy"
        onError={() => onImgError(current)}
        style={{
          width: "100%", height: "100%", objectFit: "contain",
          objectPosition: "center", display: "block",
          transition: "opacity .25s ease",
        }}
      />
      {p.badge && (
        <div style={{
          position: "absolute", top: 14, left: 14, zIndex: 2,
          background: "#2d2520", color: "#f8f6f1", padding: "4px 10px",
          fontFamily: "'Noto Sans JP', sans-serif", fontSize: 12, letterSpacing: "0.15em", fontWeight: 500, borderRadius: 2,
        }}>{p.badge}</div>
      )}
      {showCount > 1 && (
        <>
          {/* 画像枚数の小バッジ(右上) */}
          <div style={{
            position: "absolute", top: 14, right: 14, zIndex: 2,
            background: "rgba(26,22,18,.55)", color: "#fff", padding: "3px 8px",
            fontFamily: "'Cormorant Garamond', serif", fontSize: 13, letterSpacing: "0.1em", borderRadius: 999,
            display: "flex", alignItems: "center", gap: 4,
          }}>
            <svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
              <rect x="3" y="3" width="14" height="14" rx="1" />
              <rect x="7" y="7" width="14" height="14" rx="1" />
            </svg>
            {idx + 1}/{showCount}
          </div>
          {/* ドットインジケーター(下) */}
          <div style={{
            position: "absolute", bottom: 10, left: 0, right: 0, zIndex: 2,
            display: "flex", justifyContent: "center", gap: 5,
          }}>
            {okImages.map((_, i) => (
              <span key={i} style={{
                width: 6, height: 6, borderRadius: "50%",
                background: i === idx ? "#1a1612" : "rgba(26,22,18,.25)",
                transition: "background .2s ease",
              }} />
            ))}
          </div>
        </>
      )}
    </div>
  );
}

// 詳細モーダル内の写真ギャラリー（手送り: 矢印 + スワイプ + サムネイル）
function ProductGallery({ images, name }) {
  const [imgs, setImgs] = React.useState(images);
  const [idx, setIdx] = React.useState(0);
  const touchX = React.useRef(null);

  React.useEffect(() => { setImgs(images); setIdx(0); }, [images]);

  const count = imgs.length;
  const go = React.useCallback((dir) => {
    setIdx((i) => (count ? (i + dir + count) % count : 0));
  }, [count]);

  // 左右キーでも送れる
  React.useEffect(() => {
    if (count < 2) return;
    const onKey = (e) => {
      if (e.key === "ArrowLeft") go(-1);
      else if (e.key === "ArrowRight") go(1);
    };
    window.addEventListener("keydown", onKey);
    return () => window.removeEventListener("keydown", onKey);
  }, [go, count]);

  const onImgError = (url) => {
    setImgs((arr) => {
      const next = arr.filter((u) => u !== url);
      return next.length > 0 ? next : arr;
    });
    setIdx(0);
  };

  const onTouchStart = (e) => { touchX.current = e.touches[0].clientX; };
  const onTouchEnd = (e) => {
    if (touchX.current == null) return;
    const dx = e.changedTouches[0].clientX - touchX.current;
    if (Math.abs(dx) > 40) go(dx < 0 ? 1 : -1);
    touchX.current = null;
  };

  const arrow = (side) => ({
    position: "absolute", top: "50%", transform: "translateY(-50%)", [side]: 12,
    width: 42, height: 42, borderRadius: "50%",
    border: "1px solid rgba(45,37,32,0.15)", background: "rgba(255,255,255,0.92)",
    color: "#2d2520", fontSize: 26, lineHeight: 1, cursor: "pointer", zIndex: 2,
    display: "flex", alignItems: "center", justifyContent: "center",
    fontFamily: "'Cormorant Garamond', serif", boxShadow: "0 2px 12px -4px rgba(45,37,32,0.35)",
  });

  const current = imgs[idx] || imgs[0];

  return (
    <div style={{ display: "flex", flexDirection: "column", gap: 14 }}>
      <div
        onTouchStart={onTouchStart}
        onTouchEnd={onTouchEnd}
        style={{
          position: "relative", background: "#f4f0e8", borderRadius: 3,
          overflow: "hidden", aspectRatio: "4 / 3", touchAction: "pan-y",
        }}
      >
        <img
          src={current}
          alt={name}
          onError={() => onImgError(current)}
          style={{ width: "100%", height: "100%", objectFit: "contain", display: "block" }}
        />
        {count > 1 && (
          <>
            <button aria-label="前の写真" onClick={() => go(-1)} style={arrow("left")}>‹</button>
            <button aria-label="次の写真" onClick={() => go(1)} style={arrow("right")}>›</button>
            <div style={{
              position: "absolute", top: 12, right: 14, zIndex: 2,
              background: "rgba(26,22,18,.55)", color: "#fff", padding: "3px 10px",
              fontFamily: "'Cormorant Garamond', serif", fontSize: 13, letterSpacing: "0.1em", borderRadius: 999,
            }}>{idx + 1} / {count}</div>
          </>
        )}
      </div>
      {count > 1 && (
        <div style={{ display: "flex", gap: 8, flexWrap: "wrap" }}>
          {imgs.map((src, i) => (
            <button key={src + i} onClick={() => setIdx(i)} aria-label={`写真 ${i + 1}`} style={{
              width: 62, height: 62, padding: 0, borderRadius: 3, overflow: "hidden", cursor: "pointer",
              border: i === idx ? "2px solid #B8941F" : "1px solid #e0dcd4", background: "#f4f0e8", flexShrink: 0,
            }}>
              <img src={src} alt="" onError={() => onImgError(src)} style={{
                width: "100%", height: "100%", objectFit: "cover", display: "block",
                opacity: i === idx ? 1 : 0.65, transition: "opacity .2s ease",
              }} />
            </button>
          ))}
        </div>
      )}
    </div>
  );
}

// 商品詳細モーダル（A.E.Köchert 風: 白基調・余白・ゴールドアクセント）
function ProductDetailModal({ p, onClose }) {
  const { isMobile } = (window.useViewport || (() => ({ isMobile: false })))();
  const QUOTE_URL = "https://pace.co.jp/#contact";
  const images = React.useMemo(() => resolveProductImages(p), [p]);

  React.useEffect(() => {
    const prev = document.body.style.overflow;
    document.body.style.overflow = "hidden";
    const onKey = (e) => { if (e.key === "Escape") onClose(); };
    window.addEventListener("keydown", onKey);
    return () => {
      document.body.style.overflow = prev;
      window.removeEventListener("keydown", onKey);
    };
  }, [onClose]);

  const specRows = [
    ["参考価格", p.price ? p.price + "（税抜）" : ""],
    ["カートン入数", p.cartonQty],
    ["サイズ", p.size],
    ["本体色・型", p.colorType],
    ["包装", p.packaging],
    ["印刷", p.print],
    ["名入れ範囲", p.printArea],
    ["カートン入数", p.cartonQty],
    ["納期", p.delivery],
    ["送料", p.shippingFee],
    ["仕様", p.spec],
    ["掲載", p.page ? "MARCHE73 " + p.page : ""],
  ].filter(([, v]) => v != null && String(v).trim() !== "");

  return (
    <div
      onClick={onClose}
      style={{
        position: "fixed", inset: 0, zIndex: 9999, background: "rgba(26,22,18,0.72)",
        display: "flex", alignItems: isMobile ? "flex-start" : "center", justifyContent: "center",
        padding: isMobile ? 0 : 32, overflowY: "auto",
        animation: "catalogFade .3s cubic-bezier(.16,1,.3,1)",
      }}
    >
      <div
        onClick={(e) => e.stopPropagation()}
        style={{
          position: "relative", background: "#fff", width: "100%", maxWidth: 1040,
          borderRadius: isMobile ? 0 : 4, maxHeight: isMobile ? "100%" : "92vh", overflowY: "auto",
          boxShadow: "0 30px 80px -20px rgba(0,0,0,0.5)",
        }}
      >
        <button aria-label="閉じる" onClick={onClose} style={{
          position: "absolute", top: 14, right: 14, zIndex: 4,
          width: 40, height: 40, borderRadius: "50%",
          border: "1px solid rgba(45,37,32,0.15)", background: "rgba(255,255,255,0.92)",
          color: "#2d2520", fontSize: 22, lineHeight: 1, cursor: "pointer",
          display: "flex", alignItems: "center", justifyContent: "center",
        }}>×</button>

        <div style={{ display: "grid", gridTemplateColumns: isMobile ? "1fr" : "1.05fr 0.95fr" }}>
          <div style={{ padding: isMobile ? "56px 20px 20px" : "40px 0 40px 40px" }}>
            <ProductGallery images={images} name={p.name} />
          </div>

          <div style={{ padding: isMobile ? "8px 24px 40px" : "52px 44px 44px" }}>
            {p.cat && (
              <div style={{ fontFamily: "'Cormorant Garamond', serif", fontStyle: "italic", fontSize: 15, color: "#B8941F", letterSpacing: "0.18em", marginBottom: 10 }}>
                {p.cat}
              </div>
            )}
            {p.badge && (
              <span style={{
                display: "inline-block", marginBottom: 12, background: "#2d2520", color: "#f8f6f1",
                padding: "4px 12px", fontFamily: "'Noto Sans JP', sans-serif", fontSize: 12, letterSpacing: "0.15em", borderRadius: 2,
              }}>{p.badge}</span>
            )}
            <h2 style={{ fontFamily: "'Shippori Mincho', serif", fontSize: isMobile ? 24 : 28, fontWeight: 600, color: "#2d2520", margin: 0, lineHeight: 1.4 }}>
              {p.name}
            </h2>
            <div style={{ width: 40, height: 1, background: "#B8941F", marginTop: 20, marginBottom: 20 }} />
            {p.desc && (
              <p style={{ fontFamily: "'Noto Sans JP', sans-serif", fontWeight: 300, fontSize: 15, lineHeight: 1.95, color: "#3d3530", margin: "0 0 24px" }}>
                {p.desc}
              </p>
            )}
            {Array.isArray(p.serviceTags) && p.serviceTags.length > 0 && (
              <div style={{ display: "flex", flexWrap: "wrap", gap: 6, marginBottom: 24 }}>
                {p.serviceTags.map((t) => (
                  <span key={t} style={{
                    fontFamily: "'Noto Sans JP', sans-serif", fontSize: 12, color: "#fff",
                    background: "#9a7e3a", padding: "3px 10px", borderRadius: 2, letterSpacing: "0.08em",
                  }}>{t}</span>
                ))}
              </div>
            )}
            {specRows.length > 0 && (
              <dl style={{ margin: "0 0 28px", display: "grid", gridTemplateColumns: "auto 1fr", columnGap: 18 }}>
                {specRows.map(([k, v], i) => (
                  <React.Fragment key={k}>
                    <dt style={{
                      fontFamily: "'Noto Sans JP', sans-serif", fontSize: 13, color: "#6b6560",
                      padding: "11px 0", borderTop: i === 0 ? "none" : "1px solid #eee7dd", whiteSpace: "nowrap",
                    }}>{k}</dt>
                    <dd style={{
                      fontFamily: "'Noto Sans JP', sans-serif", fontSize: 14, color: "#2d2520", margin: 0,
                      padding: "11px 0", borderTop: i === 0 ? "none" : "1px solid #eee7dd", lineHeight: 1.6,
                    }}>{v}</dd>
                  </React.Fragment>
                ))}
              </dl>
            )}
            <a href={QUOTE_URL} target="_blank" rel="noopener noreferrer" style={{
              display: "block", textAlign: "center", background: "#2d2520", color: "#f8f6f1",
              padding: "16px 24px", textDecoration: "none", borderRadius: 3,
              fontFamily: "'Noto Sans JP', sans-serif", fontSize: 14, letterSpacing: "0.18em",
            }}>この商品について無料お見積り</a>
          </div>
        </div>
      </div>
    </div>
  );
}

function ProductCard({ p, onOpen }) {
  return (
    <article
      role="button"
      tabIndex={0}
      onClick={() => onOpen && onOpen(p)}
      onKeyDown={(e) => { if ((e.key === "Enter" || e.key === " ") && onOpen) { e.preventDefault(); onOpen(p); } }}
      style={{
        background: "#fff", border: "1px solid #eae7e2", borderRadius: 4, overflow: "hidden",
        display: "flex", flexDirection: "column", transition: "transform .35s cubic-bezier(.16,1,.3,1), box-shadow .35s",
        cursor: "pointer",
      }}
      onMouseEnter={(e) => { e.currentTarget.style.transform = "translateY(-4px)"; e.currentTarget.style.boxShadow = "0 12px 36px -16px rgba(45,37,32,0.18)"; }}
      onMouseLeave={(e) => { e.currentTarget.style.transform = "translateY(0)"; e.currentTarget.style.boxShadow = "none"; }}
    >
      <ProductImage p={p} />
      <div style={{ padding: "22px 22px 24px", flex: 1, display: "flex", flexDirection: "column" }}>
        <div style={{ display: "flex", justifyContent: "space-between", alignItems: "baseline", marginBottom: 6 }}>
          <div style={{ fontFamily: "'Cormorant Garamond', serif", fontStyle: "italic", fontSize: 14, color: "#B8941F", letterSpacing: "0.15em" }}>
            {p.cat}
          </div>
          {p.page && (
            <div style={{ fontFamily: "'Cormorant Garamond', serif", fontStyle: "italic", fontSize: 13, color: "#6b6560", letterSpacing: "0.15em" }}>
              MARCHE73 {p.page}
            </div>
          )}
        </div>
        {Array.isArray(p.serviceTags) && p.serviceTags.length > 0 && (
          <div style={{ display: "flex", flexWrap: "wrap", gap: 6, marginBottom: 8 }}>
            {p.serviceTags.map((t) => (
              <span key={t} style={{
                fontFamily: "'Noto Sans JP', sans-serif", fontSize: 12, color: "#fff",
                background: "#9a7e3a", padding: "2px 8px", borderRadius: 2, letterSpacing: "0.08em",
              }}>{t}</span>
            ))}
          </div>
        )}
        <h3 style={{ fontFamily: "'Shippori Mincho', serif", fontSize: 18, fontWeight: 500, color: "#2d2520", margin: 0, lineHeight: 1.5, marginBottom: 14 }}>
          {p.name}
        </h3>
        <p style={{ fontFamily: "'Noto Sans JP', sans-serif", fontWeight: 300, fontSize: 14, lineHeight: 1.85, color: "#3d3530", margin: 0, minHeight: 44 }}>
          {p.desc}
        </p>
        <div style={{
          display: "grid", gridTemplateColumns: "1fr 1fr", gap: 16, alignItems: "end",
          marginTop: "auto", paddingTop: 18, borderTop: "1px solid #eae7e2",
        }}>
          <div>
            <div style={{ fontFamily: "'Cormorant Garamond', serif", fontStyle: "italic", fontSize: 12, color: "#6b6560", letterSpacing: "0.2em", marginBottom: 4 }}>
              参考価格
            </div>
            <div style={{ fontFamily: "'Shippori Mincho', serif", fontSize: 17, fontWeight: 500, color: "#2d2520", lineHeight: 1.2 }}>
              {p.price || "—"}
              <span style={{ fontSize: 12, color: "#6b6560", marginLeft: 6, fontWeight: 400 }}>(税抜)</span>
            </div>
          </div>
          <div style={{ textAlign: "right" }}>
            <div style={{ fontFamily: "'Cormorant Garamond', serif", fontStyle: "italic", fontSize: 12, color: "#6b6560", letterSpacing: "0.2em", marginBottom: 4 }}>
              カートン入数
            </div>
            <div style={{ fontFamily: "'Noto Sans JP', sans-serif", fontSize: 14, color: "#3d3530", lineHeight: 1.3 }}>
              {p.cartonQty || "—"}
            </div>
          </div>
        </div>
        <div style={{
          marginTop: 16, display: "flex", alignItems: "center", gap: 6,
          fontFamily: "'Cormorant Garamond', serif", fontStyle: "italic", fontSize: 15,
          color: "#B8941F", letterSpacing: "0.12em",
        }}>
          詳しく見る <span style={{ fontStyle: "normal" }}>→</span>
        </div>
      </div>
    </article>
  );
}

// 商品が一切登録されていないとき(WP も静的データも空)に表示
function PreparingState() {
  return (
    <div style={{
      gridColumn: "1 / -1", padding: "100px 40px", textAlign: "center",
      border: "1px dashed #d8d3cc", borderRadius: 4, background: "#faf8f3",
    }}>
      <div style={{ fontFamily: "'Cormorant Garamond', serif", fontStyle: "italic", fontSize: 48, fontWeight: 300, color: "#B8941F", marginBottom: 20 }}>—</div>
      <h3 style={{ fontFamily: "'Shippori Mincho', serif", fontWeight: 500, fontSize: 22, color: "#2d2520", margin: 0 }}>
        商品ラインナップは準備中です
      </h3>
      <p style={{ fontFamily: "'Noto Sans JP', sans-serif", fontWeight: 300, fontSize: 16, lineHeight: 2, color: "#3d3530", marginTop: 18 }}>
        ご要望に応じて、戦略型ノベルティの最適な商品をご提案いたします。<br />
        お見積り・ご相談はお気軽にどうぞ。
      </p>
      <div style={{ display: "flex", gap: 12, justifyContent: "center", marginTop: 32 }}>
        <a href="#contact" style={{
          background: "#2d2520", color: "#f8f6f1", padding: "14px 32px", textDecoration: "none",
          fontFamily: "'Noto Sans JP', sans-serif", fontSize: 14, letterSpacing: "0.2em", borderRadius: 3,
        }}>お見積りフォーム</a>
      </div>
    </div>
  );
}

function EmptyState({ onClear }) {
  return (
    <div style={{
      gridColumn: "1 / -1", padding: "80px 40px", textAlign: "center",
      border: "1px dashed #d8d3cc", borderRadius: 4, background: "#faf8f3",
    }}>
      <div style={{ fontFamily: "'Cormorant Garamond', serif", fontStyle: "italic", fontSize: 48, fontWeight: 300, color: "#B8941F", marginBottom: 20 }}>—</div>
      <h3 style={{ fontFamily: "'Shippori Mincho', serif", fontWeight: 500, fontSize: 20, color: "#2d2520", margin: 0 }}>
        該当する商品が見つかりませんでした
      </h3>
      <p style={{ fontFamily: "'Noto Sans JP', sans-serif", fontWeight: 300, fontSize: 15, lineHeight: 2, color: "#3d3530", marginTop: 16 }}>
        条件を緩めるか、お問い合わせください。<br />
        ご要望に合わせたOEM製作のご提案も可能です。
      </p>
      <div style={{ display: "flex", gap: 12, justifyContent: "center", marginTop: 32 }}>
        <button onClick={onClear} style={{
          background: "transparent", border: "1px solid #2d2520", color: "#2d2520",
          padding: "14px 28px", fontFamily: "'Noto Sans JP', sans-serif", fontSize: 14, letterSpacing: "0.18em",
          cursor: "pointer", borderRadius: 3,
        }}>条件をクリア</button>
        <a href="#contact" style={{
          background: "#2d2520", color: "#f8f6f1", padding: "14px 28px", textDecoration: "none",
          fontFamily: "'Noto Sans JP', sans-serif", fontSize: 14, letterSpacing: "0.18em", borderRadius: 3,
        }}>お問い合わせ</a>
      </div>
    </div>
  );
}

function SelectedTags({ filters, setFilters }) {
  const all = [
    ...[...filters.scenes].map((v) => ({ axis: "scenes", v, label: v })),
    ...[...filters.budgets].map((v) => ({ axis: "budgets", v, label: BUDGETS.find((b) => b.id === v).label })),
  ];
  if (all.length === 0) return null;
  const remove = (axis, v) => {
    setFilters((f) => {
      const next = { ...f, [axis]: new Set(f[axis]) };
      next[axis].delete(v);
      return next;
    });
  };
  const clear = () => setFilters({ scenes: new Set(), budgets: new Set(), lots: new Set() });
  return (
    <div style={{ display: "flex", flexWrap: "wrap", gap: 8, alignItems: "center" }}>
      <span style={{ fontFamily: "'Noto Sans JP', sans-serif", fontSize: 13, color: "#3d3530", letterSpacing: "0.15em", marginRight: 8 }}>選択中</span>
      {all.map((t) => (
        <button key={t.axis + t.v} onClick={() => remove(t.axis, t.v)} style={{
          background: "#2d2520", color: "#f8f6f1", border: "none", padding: "8px 14px",
          fontFamily: "'Noto Sans JP', sans-serif", fontSize: 13, fontWeight: 400, letterSpacing: "0.05em",
          borderRadius: 999, cursor: "pointer", display: "inline-flex", alignItems: "center", gap: 8,
        }}>
          {t.label} <span style={{ opacity: 0.6, fontSize: 14 }}>×</span>
        </button>
      ))}
      <button onClick={clear} style={{
        background: "transparent", border: "none", color: "#3d3530",
        fontFamily: "'Noto Sans JP', sans-serif", fontSize: 13, letterSpacing: "0.1em",
        cursor: "pointer", textDecoration: "underline", marginLeft: 8,
      }}>すべてクリア</button>
    </div>
  );
}

// =========================================================
// DIRECTION A — Sidebar filter (B2B-style, sticky left rail)
// =========================================================
function CatalogA() {
  const [filters, setFilters] = useState(() => {
    const init = window.buildFiltersFromSearch ? window.buildFiltersFromSearch(window.__noveltySearch || {}) : { scenes: new Set(), budgets: new Set(), lots: new Set(), keyword: "" };
    return init;
  });
  const [productsTick, setProductsTick] = useState(0);
  const filtered = useMemo(() => (window.PRODUCTS || []).filter((p) => matchesFilters(p, filters)), [filters, productsTick]);
  const { isMobile, isTablet } = (window.useViewport || (() => ({ isMobile: false, isTablet: false })))();
  const compact = isMobile || isTablet;
  const [filterOpen, setFilterOpen] = useState(false);
  const [selected, setSelected] = useState(null);

  React.useEffect(() => {
    const onSearch = (e) => setFilters(window.buildFiltersFromSearch(e.detail));
    const onProductsLoaded = () => setProductsTick((t) => t + 1);
    window.addEventListener("novelty:search", onSearch);
    window.addEventListener("pace:products-loaded", onProductsLoaded);
    return () => {
      window.removeEventListener("novelty:search", onSearch);
      window.removeEventListener("pace:products-loaded", onProductsLoaded);
    };
  }, []);

  const toggle = (axis, v) => {
    setFilters((f) => {
      const next = { ...f, [axis]: new Set(f[axis]) };
      if (next[axis].has(v)) next[axis].delete(v); else next[axis].add(v);
      return next;
    });
  };

  return (
    <div style={{
      display: "grid",
      gridTemplateColumns: compact ? "1fr" : "300px 1fr",
      gap: compact ? 24 : 56,
      alignItems: "start",
    }}>
      {/* モバイル: 絞り込み開閉トグル */}
      {compact && (
        <button onClick={() => setFilterOpen(!filterOpen)} style={{
          background: "#2d2520", color: "#f8f6f1", border: "none",
          padding: "14px 20px", borderRadius: 3, cursor: "pointer",
          fontFamily: "'Noto Sans JP', sans-serif", fontSize: 14, letterSpacing: "0.15em", fontWeight: 500,
          width: "100%", textAlign: "left",
          display: "flex", justifyContent: "space-between", alignItems: "center",
        }}>
          <span>条件で絞り込む</span>
          <span style={{ fontSize: 18 }}>{filterOpen ? "−" : "+"}</span>
        </button>
      )}

      {/* Sidebar(モバイルは閉じる) */}
      <aside style={{
        position: compact ? "static" : "sticky", top: 100, alignSelf: "start",
        display: compact && !filterOpen ? "none" : "block",
      }}>
        <div style={{ fontFamily: "'Cormorant Garamond', serif", fontStyle: "italic", color: "#B8941F", fontSize: 15, letterSpacing: "0.2em", marginBottom: 12 }}>
          Refine
        </div>
        <h3 style={{ fontFamily: "'Shippori Mincho', serif", fontSize: 20, fontWeight: 500, color: "#2d2520", margin: 0, paddingBottom: 24, borderBottom: "1px solid #2d2520" }}>
          条件で絞り込む
        </h3>

        <FilterGroup title="利用シーン" en="Scene">
          {SCENES.map((s) => (
            <CheckRow key={s} label={s} checked={filters.scenes.has(s)} onClick={() => toggle("scenes", s)} />
          ))}
        </FilterGroup>

        <FilterGroup title="予算帯" en="Budget" sub="1個あたり">
          {BUDGETS.map((b) => (
            <RadioRow key={b.id} label={b.label} sub={b.sub} checked={filters.budgets.has(b.id)} onClick={() => toggle("budgets", b.id)} />
          ))}
        </FilterGroup>

        <button onClick={() => setFilters({ scenes: new Set(), budgets: new Set(), lots: new Set(), keyword: "" })} style={{
          width: "100%", marginTop: 16, padding: "14px 0", background: "transparent", border: "1px solid #d8d3cc",
          color: "#3d3530", fontFamily: "'Noto Sans JP', sans-serif", fontSize: 14, letterSpacing: "0.18em",
          cursor: "pointer", borderRadius: 3,
        }}>すべてクリア</button>
      </aside>

      {/* Grid */}
      <div>
        <div style={{
          display: "flex", alignItems: "center", justifyContent: "space-between",
          flexWrap: "wrap",
          paddingBottom: isMobile ? 16 : 24,
          borderBottom: "1px solid #eae7e2",
          marginBottom: isMobile ? 20 : 32, gap: 16,
        }}>
          <div style={{ display: "flex", alignItems: "baseline", gap: 10 }}>
            <span style={{ fontFamily: "'Cormorant Garamond', serif", fontSize: isMobile ? 24 : 32, fontWeight: 400, color: "#2d2520" }}>
              {filtered.length}
            </span>
            <span style={{ fontFamily: "'Noto Sans JP', sans-serif", fontSize: isMobile ? 12 : 14, color: "#3d3530", letterSpacing: "0.12em" }}>
              件 / 全 {(window.PRODUCTS || []).length} 件
            </span>
          </div>
          <select style={{
            background: "transparent", border: "1px solid #d8d3cc",
            padding: isMobile ? "8px 12px" : "10px 16px",
            fontFamily: "'Noto Sans JP', sans-serif",
            fontSize: isMobile ? 12 : 14, color: "#2d2520", borderRadius: 3,
          }}>
            <option>並び替え：おすすめ順</option>
            <option>少ロット順</option>
            <option>新着順</option>
          </select>
        </div>

        <div style={{ marginBottom: 24 }}>
          <SelectedTags filters={filters} setFilters={setFilters} />
        </div>

        <div style={{
          display: "grid",
          gridTemplateColumns: isMobile ? "1fr" : (isTablet ? "repeat(2, 1fr)" : "repeat(3, 1fr)"),
          gap: isMobile ? 16 : 24,
        }}>
          {filtered.length === 0
            ? ((window.PRODUCTS || []).length === 0
                ? <PreparingState />
                : <EmptyState onClear={() => setFilters({ scenes: new Set(), budgets: new Set(), lots: new Set(), keyword: "" })} />)
            : filtered.map((p) => <ProductCard key={p.id} p={p} onOpen={setSelected} />)}
        </div>
      </div>

      {selected && <ProductDetailModal p={selected} onClose={() => setSelected(null)} />}
    </div>
  );
}

function FilterGroup({ title, en, sub, children }) {
  return (
    <div style={{ paddingTop: 28, paddingBottom: 28, borderBottom: "1px solid #eae7e2" }}>
      <div style={{ display: "flex", alignItems: "baseline", justifyContent: "space-between", marginBottom: 16 }}>
        <div>
          <div style={{ fontFamily: "'Shippori Mincho', serif", fontSize: 16, fontWeight: 500, color: "#2d2520" }}>{title}</div>
          {sub && <div style={{ fontFamily: "'Noto Sans JP', sans-serif", fontSize: 12, color: "#6b6560", marginTop: 4 }}>{sub}</div>}
        </div>
        <div style={{ fontFamily: "'Cormorant Garamond', serif", fontStyle: "italic", color: "#B8941F", fontSize: 13, letterSpacing: "0.2em" }}>{en}</div>
      </div>
      <div style={{ display: "flex", flexDirection: "column", gap: 10 }}>{children}</div>
    </div>
  );
}

function CheckRow({ label, checked, onClick }) {
  return (
    <button onClick={onClick} style={{
      display: "flex", alignItems: "center", gap: 12, background: "none", border: "none", padding: "6px 0",
      cursor: "pointer", textAlign: "left",
    }}>
      <span style={{
        width: 16, height: 16, border: `1px solid ${checked ? "#B8941F" : "#d8d3cc"}`, borderRadius: 2,
        background: checked ? "#B8941F" : "transparent", display: "flex", alignItems: "center", justifyContent: "center",
        flexShrink: 0,
      }}>
        {checked && <span style={{ color: "#fff", fontSize: 13, lineHeight: 1, fontWeight: 600 }}>✓</span>}
      </span>
      <span style={{
        fontFamily: "'Noto Sans JP', sans-serif", fontWeight: checked ? 500 : 300, fontSize: 15,
        color: checked ? "#2d2520" : "#3d3530",
      }}>{label}</span>
    </button>
  );
}

function RadioRow({ label, sub, checked, onClick }) {
  return (
    <button onClick={onClick} style={{
      display: "flex", alignItems: "center", gap: 12, background: "none", border: "none", padding: "6px 0",
      cursor: "pointer", textAlign: "left",
    }}>
      <span style={{
        width: 16, height: 16, borderRadius: "50%", border: `1px solid ${checked ? "#B8941F" : "#d8d3cc"}`,
        display: "flex", alignItems: "center", justifyContent: "center", flexShrink: 0,
      }}>
        {checked && <span style={{ width: 8, height: 8, borderRadius: "50%", background: "#B8941F" }} />}
      </span>
      <span>
        <span style={{
          fontFamily: "'Noto Sans JP', sans-serif", fontWeight: checked ? 500 : 300, fontSize: 15,
          color: checked ? "#2d2520" : "#3d3530",
        }}>{label}</span>
        {sub && <span style={{ marginLeft: 8, fontFamily: "'Noto Sans JP', sans-serif", fontSize: 12, color: "#6b6560" }}>{sub}</span>}
      </span>
    </button>
  );
}

Object.assign(window, { CatalogA, ProductCard, ProductImage, ProductGallery, ProductDetailModal, EmptyState, SelectedTags });
