/* Denky — 공용 UI 컴포넌트 */
const { useState, useEffect, useRef } = React;

/* ---------- 아이콘 (간단 라인 아이콘) ---------- */
const ICONS = {
  home: "M3 11.2 12 4l9 7.2M5.4 9.8V20h4.8v-5h3.6v5h4.8V9.8",
  search: "M11 19a8 8 0 1 0 0-16 8 8 0 0 0 0 16Zm10 2-4.3-4.3",
  cart: "M3 4h2l2.4 12.3a1 1 0 0 0 1 .7h9.2a1 1 0 0 0 1-.8L21 8H6",
  user: "M12 12a4 4 0 1 0 0-8 4 4 0 0 0 0 8Zm-7 8a7 7 0 0 1 14 0",
  heart: "M12 20s-7-4.6-9.3-9.2C1.1 7.6 2.8 4.5 6 4.5c2 0 3.2 1.2 4 2.3.8-1.1 2-2.3 4-2.3 3.2 0 4.9 3.1 3.3 6.3C19 15.4 12 20 12 20Z",
  sparkle: "M12 3l1.8 5.2L19 10l-5.2 1.8L12 17l-1.8-5.2L5 10l5.2-1.8L12 3Z",
  arrowR: "M5 12h14m-6-6 6 6-6 6",
  arrowD: "M12 5v14m-6-6 6 6 6-6",
  check: "M5 12.5l4.5 4.5L19 7",
  close: "M6 6l12 12M18 6 6 18",
  chevD: "M6 9l6 6 6-6",
  chevR: "M9 6l6 6-6 6",
  chevL: "M15 6l-6 6 6 6",
  grid: "M4 4h7v7H4zM13 4h7v7h-7zM4 13h7v7H4zM13 13h7v7h-7z",
  filter: "M4 5h16M7 12h10M10 19h4",
  camera: "M3 8a2 2 0 0 1 2-2h2l1.5-2h7L17 6h2a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8Zm9 9a3.5 3.5 0 1 0 0-7 3.5 3.5 0 0 0 0 7Z",
  star: "M12 3l2.6 5.5 6 .8-4.4 4.2 1.1 6L12 17.6 6.7 19.5l1.1-6L3.4 9.3l6-.8L12 3Z",
  layers: "M12 3 3 8l9 5 9-5-9-5Zm9 9-9 5-9-5m18 4-9 5-9-5",
  trash: "M5 7h14M9 7V5a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v2m-8 0 1 13a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1l1-13",
  shield: "M12 3l8 3v6c0 5-3.4 8-8 9-4.6-1-8-4-8-9V6l8-3Z",
  ruler: "M4 14 14 4l6 6L10 20 4 14Zm3 0 1.5 1.5M10 11l1.5 1.5M13 8l1.5 1.5",
  bag: "M6 8h12l-1 12H7L6 8Zm3 0V6a3 3 0 0 1 6 0v2",
  plus: "M12 5v14M5 12h14",
  eye: "M2 12s3.6-7 10-7 10 7 10 7-3.6 7-10 7S2 12 2 12Zm10 3a3 3 0 1 0 0-6 3 3 0 1 0 0 6Z",
  eyeoff: "M3 3l18 18M10.6 10.6a3 3 0 0 0 3.8 3.8M9.4 5.2A10.5 10.5 0 0 1 12 5c6.4 0 10 7 10 7a18 18 0 0 1-4 4.7M6.2 6.6A18 18 0 0 0 2 12s3.6 7 10 7c1.4 0 2.7-.2 3.9-.6",
};
function Icon({ name, size = 20, stroke = 2, fill = "none", style }) {
  return (
    <svg width={size} height={size} viewBox="0 0 24 24" fill={fill}
      stroke="currentColor" strokeWidth={stroke} strokeLinecap="round" strokeLinejoin="round"
      style={{ flex: "none", ...style }} aria-hidden="true">
      <path d={ICONS[name]} />
    </svg>
  );
}

/* ---------- 이미지 플레이스홀더 (제품 색상 톤 기반) ---------- */
function PhImg({ tone = "#ECEEF1", ink = "#9CA3AF", label, ratio, type, className = "" }) {
  // 대각 스트라이프 + 옷 종류에 따른 매우 단순한 도형 실루엣
  // 패턴 id 는 렌더마다 새로 만들지 않고(Math.random 지양) React.useId 로 인스턴스당 고정 —
  // 매 렌더 새 id 가 나오면 DOM 재사용이 깨지고 불필요한 리페인트가 생깁니다.
  const id = "g" + React.useId().replace(/:/g, "");
  return (
    <div className={"ph ph-img " + className} style={{ background: tone }}>
      <svg width="100%" height="100%" viewBox="0 0 200 250" preserveAspectRatio="xMidYMid slice"
        style={{ position: "absolute", inset: 0 }} aria-hidden="true">
        <defs>
          <pattern id={id} width="14" height="14" patternUnits="userSpaceOnUse" patternTransform="rotate(45)">
            <rect width="14" height="14" fill={tone} />
            <line x1="0" y1="0" x2="0" y2="14" stroke={ink} strokeOpacity="0.07" strokeWidth="7" />
          </pattern>
        </defs>
        <rect width="200" height="250" fill={`url(#${id})`} />
        <g fill="none" stroke={ink} strokeOpacity="0.28" strokeWidth="2.4" strokeLinejoin="round" strokeLinecap="round" transform="translate(100,118)">
          {type === "상의" && <path d="M-34-34 -52-22 -42-6 -28-14 -28 44 28 44 28-14 42-6 52-22 34-34 22-40q-22 12-44 0z" />}
          {type === "하의" && <path d="M-26-40h52v18l-8 62h-16l-2-50-2 50h-16l-8-62z" />}
          {type === "원피스" && <path d="M-22-40q22 10 44 0l10 18-12 8 6 66h-52l6-66-12-8z" />}
          {type === "아우터" && <path d="M-36-32 -54-18 -44 0-32-8v52h64V-8l12 8 10-18-18-14-22-8q-18 8-36 0z M0-40v84" />}
        </g>
      </svg>
      {label && <span className="ph-label">{label}</span>}
    </div>
  );
}

/* ---------- 버튼 ---------- */
function Btn({ variant = "primary", size, block, icon, iconR, children, ...p }) {
  const cls = ["btn", `btn-${variant}`, size && `btn-${size}`, block && "btn-block"].filter(Boolean).join(" ");
  return (
    <button className={cls} {...p}>
      {icon && <Icon name={icon} size={size === "lg" ? 20 : 18} />}
      {/* 라벨은 따로 감싼다 — 상품명처럼 긴 글자가 버튼 밖으로 삐져나오지 않고
          말줄임(…)으로 잘리게 하기 위해서다. (스마트앱 DenkyButton 과 같은 동작) */}
      <span className="btn-label">{children}</span>
      {iconR && <Icon name={iconR} size={size === "lg" ? 20 : 18} />}
    </button>
  );
}

/* 버튼에 넣을 상품명 줄이기 — 너무 길면 뒤를 잘라 '… 사러가기' 처럼 동작 단어가 남게 한다.
   (버튼 자체에도 말줄임이 걸려 있지만, 그러면 '사러가기'까지 잘려 무슨 버튼인지 알기 어렵다) */
function shortName(name, max = 16) {
  const s = String(name || "").trim();
  return s.length > max ? s.slice(0, max) + "…" : s;
}

/* ---------- 가격 ---------- */
function Price({ value, lg }) {
  return (
    <span className={"price" + (lg ? " price-lg" : "")}>
      {DENKY.won(value)}<span className="unit">원</span>
    </span>
  );
}

/* ---------- 가격(할인 표시) ---------- */
function PriceTag({ price, discount, lg }) {
  const hasDisc = discount != null && discount > 0 && discount < price;
  if (!hasDisc) return <Price value={price} lg={lg} />;
  const pct = Math.round((1 - discount / price) * 100);
  return (
    <span style={{ display: "flex", flexDirection: "column", gap: 2 }}>
      <span className="row" style={{ gap: 6, alignItems: "baseline" }}>
        <span style={{ color: "#E0245E", fontWeight: 800, fontSize: lg ? 22 : 14 }}>{pct}%</span>
        <Price value={discount} lg={lg} />
      </span>
      <span style={{ fontSize: lg ? 14 : 12, color: "var(--sub)", textDecoration: "line-through" }}>{DENKY.won(price)}원</span>
    </span>
  );
}

/* ---------- 별점 ---------- */
function Rating({ value, count, size = 13 }) {
  return (
    <span className="row" style={{ gap: 5, color: "var(--sub)", fontSize: 12.5 }}>
      <Icon name="star" size={size} fill="var(--warn)" stroke={0} style={{ color: "var(--warn)" }} />
      <strong style={{ color: "var(--ink)", fontWeight: 600 }}>{value}</strong>
      {count != null && <span>({count})</span>}
    </span>
  );
}

// R2(Cloudflare) 공개 CDN 베이스 — 위탁 상품 썸네일을 엣지에서 바로 서빙(WebP 330px).
// 비우면 예전처럼 프록시 사용. (실측: 콜드 프록시 10초 → R2 2.3초)
const R2_PUBLIC_BASE = "https://pub-ea5f37b30c36458198557f8724f4a54f.r2.dev";

/* 카드 썸네일 URL — 위탁 상품은 R2 CDN 썸네일(products/{id}/0.webp) 직접(프록시보다 빠름),
   그 외(입점·자체)는 기존 프록시 &w= 축소본. p는 상품 객체(id·saleType·image 보유). */
function thumbSrc(p, w) {
  if (R2_PUBLIC_BASE && p && p.saleType === "consignment" && p.id) {
    return R2_PUBLIC_BASE + "/products/" + p.id + "/0.webp";
  }
  const url = p && p.image;
  return (url && url.includes("/products/image?")) ? url + "&w=" + w : url;
}

/* ---------- 상품 카드 ---------- */
function ProductCard({ p, onOpen, onTry, showInfo = true }) {
  // 이미지 로드 실패(프록시/외부 차단 등)면 숨기지 말고 실루엣 플레이스홀더로 대체합니다.
  const [imgFailed, setImgFailed] = useState(false);
  // 찜(하트) 상태 — API 의 찜 id 캐시에서 초기값을 읽고, 토글하면 서버와 함께 갱신
  const [wished, setWished] = useState(API.wishHas(p.id));
  async function onHeart(e) {
    e.stopPropagation();
    const t = window.denkyToast;
    try {
      const r = await API.toggleWish(p.id);
      if (r === "login") { if (t) t("로그인하면 찜할 수 있어요"); return; }
      setWished(r === "added");
      if (t) t(r === "added" ? "찜했어요" : "찜을 해제했어요");
    } catch (err) {
      if (t) t(err.message || "잠시 후 다시 시도해 주세요");
    }
  }
  return (
    <article className="pcard" onClick={() => onOpen(p)}>
      <div className="thumb">
        {(p.image && !imgFailed)
          ? <img src={thumbSrc(p, 600)} alt={p.name} loading="lazy"
              style={{ position: "absolute", inset: 0, width: "100%", height: "100%", objectFit: "cover" }}
              onError={() => setImgFailed(true)} />
          : <PhImg tone={p.tone} ink={p.toneInk} type={p.type} label={p.type || "상품"} />}
        {p.popular && (
          <span className="chip chip-soft" style={{ position: "absolute", top: 12, left: 12, height: 26, fontSize: 11.5, fontWeight: 700 }}>인기</span>
        )}
        {/* 찜(하트) — 스마트앱과 계정 공유. 마이 > 찜 탭에 모입니다 */}
        <button type="button" className={"wish-btn" + (wished ? " on" : "")} aria-label="찜" onClick={onHeart}>
          <Icon name="heart" size={16} fill={wished ? "currentColor" : "none"} />
        </button>
      </div>
      {showInfo && (
        <div className="meta">
          {/* 판매자명(닉네임) — 항상 표시: 입점 스토어명 → 브랜드 → '입어봐'(위탁·자체 큐레이션) */}
          <p className="brand">{(p.storeName && p.storeName.trim()) || (p.brand && p.brand.trim()) || "입어봐"}</p>
          <h3 className="nm">{p.name}</h3>
          <div className="row" style={{ justifyContent: "space-between", alignItems: "flex-end" }}>
            <PriceTag price={p.price} discount={p.discountPrice} />
            <Rating value={p.rating != null ? p.rating : "신규"} count={p.reviews || undefined} />
          </div>
        </div>
      )}
    </article>
  );
}

/* ---------- 체크박스 ---------- */
function Check({ checked, onChange, label, count }) {
  return (
    <label className="check">
      <input type="checkbox" checked={checked} onChange={(e) => onChange(e.target.checked)} />
      <span className="box"><Icon name="check" size={13} stroke={3} /></span>
      <span className="lbl">{label}</span>
      {count != null && <span className="cnt">{count}</span>}
    </label>
  );
}

/* ---------- 칩 ---------- */
function Chip({ children, soft, outline, onRemove }) {
  const cls = ["chip", soft && "chip-soft", outline && "chip-outline"].filter(Boolean).join(" ");
  return (
    <span className={cls}>
      {children}
      {onRemove && <button className="x" onClick={onRemove} aria-label="삭제">×</button>}
    </span>
  );
}

/* ---------- 카테고리별 의류 일러스트 ---------- */
const GARMENTS = {
  "티셔츠": <g><path d="M40 16 28 21 14 39 24 49 34 41 34 106 66 106 66 41 76 49 86 39 72 21 60 16C57 25 43 25 40 16Z" /></g>,
  "니트": <g>
    <rect x="42" y="9" width="16" height="11" rx="3" />
    <path d="M40 18 27 23 12 66 25 74 34 52 34 104 66 104 66 52 75 74 88 66 73 23 60 18C57 26 43 26 40 18Z" />
    <g fill="none"><path d="M34 66H66M34 80H66M34 94H66" /></g>
  </g>,
  "맨투맨": <g>
    <path d="M40 16 27 21 12 66 25 74 34 52 34 104 66 104 66 52 75 74 88 66 73 21 60 16C57 25 43 25 40 16Z" />
    <g fill="none"><path d="M19 70 29 74M81 70 71 74M34 98H66" /></g>
  </g>,
  "후드티": <g>
    <path d="M40 18 27 23 12 66 25 74 34 52 34 104 66 104 66 52 75 74 88 66 73 23 60 18 50 28Z" />
    <path d="M40 18C37 5 63 5 60 18L50 28Z" />
    <g fill="none"><path d="M46 26 47 40M54 26 53 40M39 80H61V92Q50 97 39 92Z" /></g>
  </g>,
  "셔츠": <g>
    <path d="M42 16 28 21 14 41 24 51 34 43 34 106 66 106 66 43 76 51 86 41 72 21 58 16 50 26Z" />
    <path d="M42 16 50 27 58 16" fill="none" />
    <g fill="none"><path d="M50 30V102" /><circle cx="50" cy="44" r="1.6" fill="currentColor" /><circle cx="50" cy="60" r="1.6" fill="currentColor" /><circle cx="50" cy="76" r="1.6" fill="currentColor" /></g>
  </g>,
  "블라우스": <g>
    <path d="M40 16 28 22 16 42 25 50 34 44 34 104Q50 110 66 104L66 44 75 50 84 42 72 22 60 16 50 27Z" />
    <path d="M44 17 50 27 56 17" fill="none" />
    <path d="M50 27 45 35 50 40 55 35Z" fill="none" />
  </g>,
  "원피스": <g>
    <path d="M40 15 29 20 18 40 27 48 35 42 38 56 20 112 80 112 62 56 65 42 73 48 82 40 71 20 60 15C57 24 43 24 40 15Z" />
    <g fill="none"><path d="M38 56 62 56" /></g>
  </g>,
  "바지": <g>
    <rect x="32" y="13" width="36" height="9" rx="2" />
    <path d="M32 21 68 21 65 110 52 110 50 58 48 110 35 110Z" />
    <g fill="none"><path d="M50 22V56" /></g>
  </g>,
  "청바지": <g>
    <rect x="32" y="13" width="36" height="9" rx="2" />
    <path d="M32 21 68 21 65 110 52 110 50 58 48 110 35 110Z" />
    <g fill="none"><path d="M50 22V56M36 26Q43 34 36 40M64 26Q57 34 64 40" /><circle cx="50" cy="18" r="1.5" fill="currentColor" /></g>
  </g>,
  "스커트": <g>
    <rect x="35" y="13" width="30" height="9" rx="2" />
    <path d="M35 21 65 21 75 106 25 106Z" />
    <g fill="none"><path d="M44 22 41 106M56 22 59 106M50 22V106" /></g>
  </g>,
  "자켓": <g>
    <path d="M40 16 24 22 14 46 24 52 33 42 33 104 67 104 67 42 76 52 86 46 76 22 60 16 50 30Z" />
    <path d="M40 16 50 30 41 60M60 16 50 30 59 60" fill="none" />
    <g fill="none"><circle cx="50" cy="74" r="1.7" fill="currentColor" /><circle cx="50" cy="86" r="1.7" fill="currentColor" /></g>
  </g>,
  "코트": <g>
    <path d="M40 14 24 20 15 46 24 52 33 42 33 116 67 116 67 42 76 52 85 46 76 20 60 14 50 28Z" />
    <path d="M40 14 50 28 42 62M60 14 50 28 58 62" fill="none" />
    <g fill="none"><path d="M22 74H78" /><circle cx="50" cy="46" r="1.7" fill="currentColor" /><circle cx="50" cy="62" r="1.7" fill="currentColor" /><circle cx="50" cy="90" r="1.7" fill="currentColor" /></g>
  </g>,
  "패딩": <g>
    <path d="M40 16Q50 9 60 16L63 24 75 30 84 48 76 54 70 44 70 104 30 104 30 44 24 54 16 48 25 30 37 24Z" />
    <g fill="none"><path d="M30 44H70M30 60H70M30 76H70M30 90H70M50 28V104" /></g>
  </g>,
  "가디건": <g>
    <path d="M40 16 26 21 14 62 25 70 34 50 34 104 66 104 66 50 75 70 86 62 74 21 60 16 50 30Z" />
    <path d="M40 16 50 30 50 104M60 16 50 30" fill="none" />
    <g fill="none"><circle cx="44" cy="46" r="1.6" fill="currentColor" /><circle cx="44" cy="62" r="1.6" fill="currentColor" /><circle cx="44" cy="78" r="1.6" fill="currentColor" /></g>
  </g>,
  "점퍼": <g>
    <path d="M40 20 27 24 15 50 25 56 34 46 34 92 66 92 66 46 75 56 85 50 73 24 60 20 50 30Z" />
    <path d="M40 20 50 30 60 20" fill="none" />
    <rect x="34" y="92" width="32" height="11" rx="2" />
    <g fill="none"><path d="M50 30V92" /></g>
  </g>,
  "트레이닝세트": <g>
    <path d="M42 12 32 15 22 38 30 43 37 32 37 60 63 60 63 32 70 43 78 38 68 15 58 12 50 20Z" />
    <path d="M50 20V60" fill="none" />
    <rect x="36" y="66" width="28" height="6" rx="2" />
    <path d="M36 71 64 71 62 114 53 114 51 80 49 114 38 114Z" />
    <g fill="none"><path d="M30 36 35 39M70 36 65 39M44 66 42 114M56 66 58 114" /></g>
  </g>,
  "정장": <g>
    <path d="M40 14 24 20 15 46 24 52 33 42 33 110 67 110 67 42 76 52 85 46 76 20 60 14 50 28Z" />
    <path d="M40 14 50 28 41 64 33 60M60 14 50 28 59 64 67 60" fill="none" />
    <path d="M50 28 45 36 50 100 55 36Z" fill="currentColor" fillOpacity="0.85" stroke="none" />
  </g>,
};
function GarmentIcon({ label, tone = "#EDE9FE", ink = "#9CA3AF" }) {
  return (
    <div className="ph" style={{ background: tone }}>
      <svg viewBox="0 0 100 125" width="62%" height="62%" style={{ display: "block" }} aria-hidden="true">
        <g fill="#fff" fillOpacity="0.78" stroke={ink} strokeWidth="3" strokeLinejoin="round" strokeLinecap="round" style={{ color: ink }}>
          {GARMENTS[label] || GARMENTS["티셔츠"]}
        </g>
      </svg>
    </div>
  );
}


/* ---------- 카테고리 메가메뉴 (네이버식 2단 호버 플라이아웃) ---------- */
function CategoryMenu({ go, active: tabActive }) {
  // 주의: 'active'는 아래 호버 그룹 상태와 이름이 겹치므로 tabActive 로 받는다.
  const groups = DENKY.labelFilters;          // {그룹: [값...]}
  const names = Object.keys(groups);
  const [open, setOpen] = useState(false);
  const [active, setActive] = useState(names[0]);  // 호버 중인 그룹(왼쪽)
  const timer = useRef(null);

  // 마우스가 잠깐 벗어나도 바로 안 닫히게 약간의 지연을 둡니다.
  const openNow = () => { if (timer.current) clearTimeout(timer.current); setOpen(true); };
  const closeSoon = () => { timer.current = setTimeout(() => setOpen(false), 140); };

  // 값 클릭 → 그 라벨 조건으로 카탈로그 바로 이동
  const pick = (group, value) => { setOpen(false); go("catalog", { labelFilter: { [group]: [value] } }); };

  return (
    <div className="cat-menu" onMouseEnter={openNow} onMouseLeave={closeSoon}>
      {/* 트리거도 헤더 탭과 같은 모양 — 펼침 메뉴 기능은 그대로 (호버/클릭으로 열림) */}
      <button className={"hdr-tab" + (open || tabActive ? " on" : "")} onClick={() => setOpen((o) => !o)} aria-expanded={open}>
        <span className="tab-ic"><Icon name="grid" size={19} /></span>
        <span className="tab-lb">카테고리</span>
      </button>
      {open && (
        <div className="cat-flyout" role="menu">
          <ul className="cat-groups">
            {names.map((g) => (
              <li key={g} className={"cat-group" + (g === active ? " on" : "")}
                onMouseEnter={() => setActive(g)}>
                <span>{g}</span><Icon name="chevR" size={14} />
              </li>
            ))}
          </ul>
          <div className="cat-panel">
            <div className="cat-panel-head">{active}</div>
            <div className="cat-values">
              {(groups[active] || []).map((v) => (
                <button key={v} className="cat-value" onClick={() => pick(active, v)}>
                  {active === "색상" && <span className="cat-sw" style={{ background: DENKY.labelColors[v] }} />}
                  {v}
                </button>
              ))}
            </div>
            <button className="cat-all" onClick={() => { setOpen(false); go("catalog"); }}>
              전체 상품 보기 <Icon name="arrowR" size={15} />
            </button>
          </div>
        </div>
      )}
    </div>
  );
}

/* ---------- 헤더 탭 (스마트앱 하단 탭과 같은 모양 — 아이콘 위 + 라벨 아래, 활성=파란 알약) ---------- */
function HdrTab({ icon, label, active, onClick, badge }) {
  return (
    <button className={"hdr-tab" + (active ? " on" : "")} onClick={onClick} aria-label={label}>
      <span className="tab-ic">
        <Icon name={icon} size={19} />
        {badge > 0 && <span className="badge">{badge}</span>}
      </span>
      <span className="tab-lb">{label}</span>
    </button>
  );
}

/* ---------- 헤더 ---------- */
function Header({ route, go, cartCount, query, setQuery, onSearch, auth }) {
  const [scrolled, setScrolled] = useState(false);
  useEffect(() => {
    const el = document.querySelector(".app-scroll") || window;
    const onScroll = () => {
      const y = el === window ? window.scrollY : el.scrollTop;
      setScrolled(y > 8);
    };
    el.addEventListener("scroll", onScroll);
    return () => el.removeEventListener("scroll", onScroll);
  }, []);
  return (
    <header className={"hdr" + (scrolled ? " scrolled" : "")}>
      <div className="wrap hdr-inner">
        <a className="logo" href="#" onClick={(e) => { e.preventDefault(); go("home"); }}>
          <img className="logo-lockup" src="assets/logo-lockup.png" alt="ibeobwa" />
        </a>
        <div className="hdr-search">
          <Icon name="search" size={19} style={{ color: "var(--sub)" }} />
          <input placeholder="미니멀한 검정 코트…" value={query}
            onChange={(e) => setQuery(e.target.value)}
            onKeyDown={(e) => { if (e.key === "Enter") onSearch(); }} />
          <Btn variant="primary" size="sm" onClick={onSearch}>검색</Btn>
        </div>
        <div className="spacer"></div>
        {/* 스마트앱 하단 탭(홈/카테고리/장바구니/마이)과 같은 모양의 헤더 탭 — 로그인 버튼 없음
            (로그인은 '마이' 진입 시 로그인 화면으로 유도) */}
        <nav className="hdr-nav">
          <HdrTab icon="home" label="홈" active={route.name === "home"} onClick={() => go("home")} />
          {/* 카테고리 메뉴 삭제 — 카테고리는 홈의 카테고리 레일/캐러셀로 접근 */}
          <HdrTab icon="cart" label="장바구니" badge={cartCount}
            active={route.name === "cart" || route.name === "checkout"} onClick={() => go("cart")} />
          <HdrTab icon="user" label="마이"
            active={route.name === "mypage" || route.name === "login" || route.name === "signup"} onClick={() => go("mypage")} />
          {/* 헤더의 '피팅 N회' 잔여횟수 표시는 제거 — 잔여 횟수는 마이페이지에서 확인 (사용자 요청 07-27) */}
        </nav>
      </div>
    </header>
  );
}

/* ---------- 사업자 정보 (전자상거래법 표시 의무 — 실제 값으로 한 곳에서 교체) ---------- */
// ▼▼ 오픈 전 아래 {{...}} 를 실제 사업자 정보로 교체하세요 ▼▼
const COMPANY = {
  name: "덴키",
  ceo: "박세현",
  bizNo: "771-05-03399",
  salesNo: "면제(간이과세자)",   // 공정위 고시상 간이과세자는 통신판매업 신고 면제 — 일반과세 전환 시 신고 후 번호로 교체
  addr: "경기도 남양주시 오남읍 진건오남로 781-17, 104동 806호(두산아파트)",
  tel: "010-6285-0378",
  email: "denky@ibeobwa.com",
  host: "Supabase Inc. (호스팅)",
  privacyOfficer: "박세현",
  privacyContact: "denky@ibeobwa.com",
  escrow: "{{구매안전(에스크로)서비스 가입 정보}}",  // 토스 계약 후 이용확인증 내용으로 교체
  effectiveDate: "2026-06-06",
};

/* ---------- 약관 / 개인정보처리방침 / 청약철회 (개인정보보호위 표준 양식 참고 초안) ---------- */
const LEGAL_DOCS = {
  terms: {
    title: "이용약관",
    body: `${COMPANY.name} 이용약관 (시행일: ${COMPANY.effectiveDate})

제1조(목적) 본 약관은 ${COMPANY.name}(이하 "회사")가 제공하는 가상피팅 기반 패션 커머스 서비스(이하 "서비스")의 이용 조건 및 절차를 정합니다.

제2조(회원가입) 이용자는 본 약관 및 개인정보 수집·이용에 동의하고 가입 절차를 완료함으로써 회원이 됩니다. 만 14세 미만은 가입할 수 없습니다.

제3조(서비스) 회사는 상품 정보 제공, 가상피팅, 주문·결제 등을 제공합니다. 모든 상품은 회사 서비스 내에서 직접 결제하며, 구매·배송·환불은 본 약관 및 관련 법령에 따릅니다. 일부 상품은 위탁(배송대행) 방식으로, 공급사가 회원에게 직접 발송할 수 있습니다.

제4조(가상피팅·쿠폰) 기본 가상피팅은 무료이며, 이용자별 주간 이용 횟수 및 누적 무료 한도에 제한이 있을 수 있습니다. 구매확정 시 피팅 횟수 쿠폰이 발급될 수 있으며, 쿠폰은 유효기간 내에만 사용 가능하고 현금으로 환급되지 않습니다.

제5조(이용자의 의무) 타인의 정보 도용, 부정 결제, 서비스 방해, 권리를 침해하는 콘텐츠(사진 등) 업로드를 금지합니다.

제6조(청약철회·환불) 회원은 전자상거래법에 따라 청약철회를 할 수 있으며, 자세한 내용은 '청약철회·교환·환불 안내'에 따릅니다.

제7조(책임의 한계) 가상피팅 결과는 참고용 합성 이미지로 실제 착용과 다를 수 있습니다.

제8조(약관 변경) 회사는 약관을 개정할 수 있으며, 변경 시 시행 전 공지합니다.

[사업자 정보]
- 상호: ${COMPANY.name} / 대표자: ${COMPANY.ceo}
- 사업자등록번호: ${COMPANY.bizNo} / 통신판매업 신고: ${COMPANY.salesNo}
- 주소: ${COMPANY.addr} / 연락처: ${COMPANY.tel} / 이메일: ${COMPANY.email}`,
  },
  privacy: {
    title: "개인정보처리방침",
    body: `${COMPANY.name} 개인정보처리방침

${COMPANY.name}(이하 "회사")는 「개인정보 보호법」 제30조에 따라 정보주체의 개인정보를 보호하고 관련 고충을 신속히 처리하기 위하여 다음과 같이 개인정보 처리방침을 수립·공개합니다.

제1조(개인정보의 처리 목적)
회사는 다음 목적으로 개인정보를 처리하며, 목적 외 용도로는 이용하지 않습니다.
1. 회원 가입 및 관리(본인 식별·인증, 부정이용 방지)
2. 가상피팅 서비스 제공(사진·체형 기반 이미지 합성)
3. 재화·서비스 제공(주문·결제·배송)
4. 고충처리(문의 응대, 분쟁 처리)
5. 마케팅 및 광고(동의한 경우에 한함)

제2조(처리하는 개인정보 항목)
1. 필수: 이메일, 비밀번호(암호화 저장)
2. 선택: 이름, 전화번호, 성별, 키, 몸무게, 앞모습 사진·체형 이미지, 배송지(주소·연락처)
3. 결제·주문: 주문내역, 결제수단 정보(결제대행사가 처리), 배송정보
4. 자동수집: 접속 로그, 쿠키/로컬스토리지(로그인 유지)

제3조(개인정보의 처리 및 보유 기간)
회원 탈퇴 시 지체 없이 파기함을 원칙으로 합니다. 다만 관계 법령에 따라 다음과 같이 보관합니다.
- 계약 또는 청약철회 등에 관한 기록: 5년 (전자상거래법)
- 대금결제 및 재화 등의 공급에 관한 기록: 5년 (전자상거래법)
- 소비자 불만 또는 분쟁처리에 관한 기록: 3년 (전자상거래법)
- 접속 로그: 3개월 (통신비밀보호법)

제4조(개인정보의 제3자 제공)
회사는 원칙적으로 개인정보를 제3자에게 제공하지 않으며, 다음의 경우에 한해 제공합니다.
- 위탁(배송대행) 상품 발송: 공급사(도매꾹 판매자)에 수령인 이름·주소·연락처 제공 (배송 목적)
- 법령에 근거가 있거나 정보주체의 동의가 있는 경우

제5조(개인정보 처리의 위탁)
- 수탁자: Supabase Inc. / 위탁 업무: 데이터·이미지 저장 및 호스팅
- 수탁자: (주)토스페이먼츠 / 위탁 업무: 결제 처리 및 결제 도용 방지
- 수탁자: Google LLC / 위탁 업무: AI 이미지 생성(제6조 국외 이전 참조)
- 수탁자: Cloudflare, Inc. / 위탁 업무: 웹 호스팅·이미지 전송(CDN)
- 수탁자: 택배사 / 위탁 업무: 상품 배송

제6조(개인정보의 국외 이전)
회사는 가상피팅 제공을 위해 아래와 같이 개인정보를 국외로 이전합니다.
- 이전받는 자: Google LLC (Gemini API) — privacy.google.com
- 이전 국가: 미국
- 이전 항목: 가상피팅·아바타 생성에 사용하는 사진(앞모습·측면), 체형 정보(성별·키·몸무게·신체 둘레), 의류 이미지 및 생성된 결과 이미지
- 이전 일시 및 방법: 가상피팅 요청 시 정보통신망을 통해 전송
- 이전 목적: 가상피팅(이미지 합성) 처리
- 보유·이용 기간: 처리 목적 달성 시까지(처리 후 단기 보관 후 삭제)
정보주체는 국외 이전을 거부할 수 있으며, 이 경우 사진(또는 신체정보 기반 아바타) 가상피팅 기능 이용이 제한될 수 있습니다.

제7조(정보주체의 권리·의무 및 행사방법)
정보주체는 언제든지 개인정보 열람·정정·삭제·처리정지 및 동의 철회를 요청할 수 있습니다. 마이페이지 또는 개인정보 보호책임자에게 요청할 수 있으며 회사는 지체 없이 조치합니다. 마케팅 수신 동의는 마이페이지에서 즉시 철회할 수 있습니다.

제8조(개인정보의 파기)
보유기간 경과·처리목적 달성 시 지체 없이 파기합니다. 전자적 파일은 복구 불가능한 방법으로 삭제하고, 출력물은 분쇄·소각합니다.

제9조(개인정보의 안전성 확보 조치)
- 관리적: 내부관리계획 수립, 접근권한 최소화
- 기술적: 비밀번호 암호화(bcrypt), 접근통제, 전송구간 암호화(HTTPS)
- 물리적: 자료 보관장소 접근통제

제10조(쿠키 등 자동수집 장치)
회사는 로그인 유지 등을 위해 쿠키/로컬스토리지를 사용합니다. 이용자는 브라우저 설정에서 저장을 거부할 수 있으나, 일부 기능 이용이 제한될 수 있습니다.

제11조(개인정보 보호책임자)
- 보호책임자: ${COMPANY.privacyOfficer}
- 연락처: ${COMPANY.privacyContact}

제12조(권익침해 구제방법)
- 개인정보분쟁조정위원회: 1833-6972 (www.kopico.go.kr)
- 개인정보침해신고센터(KISA): 118 (privacy.kisa.or.kr)
- 대검찰청 사이버수사과 1301 / 경찰청 사이버수사국 182

제13조(시행 및 변경)
본 방침은 ${COMPANY.effectiveDate}부터 시행합니다. 변경 시 시행 7일 전 공지합니다.`,
  },
  overseas: {
    title: "개인정보 국외 이전 동의",
    body: `개인정보 국외 이전에 관한 동의

가상피팅 기능 제공을 위해 아래와 같이 개인정보가 국외로 이전됩니다.
- 이전받는 자: Google LLC (Gemini API) — privacy.google.com
- 이전 국가: 미국
- 이전 항목: 가상피팅·아바타 생성에 사용하는 사진(앞모습·측면), 체형 정보(성별·키·몸무게·신체 둘레), 의류 이미지 및 생성된 결과 이미지
- 이전 일시·방법: 가상피팅 요청 시 정보통신망을 통해 전송
- 이전 목적: 가상피팅(이미지 합성) 처리
- 보유·이용 기간: 처리 목적 달성 시까지(처리 후 단기 보관 후 삭제)

귀하는 위 국외 이전에 동의하지 않을 수 있습니다. 다만 미동의 시 사진(또는 신체정보 기반 아바타) 가상피팅 기능 이용이 제한될 수 있습니다.
(신체정보 기반 아바타 생성도 동일하게 Google Gemini에서 처리됩니다.)`,
  },
  refund: {
    title: "청약철회·교환·환불 안내",
    body: `청약철회·교환·환불 및 구매안전 안내

1. 청약철회
- 상품을 공급받은 날부터 7일 이내 청약철회(반품)를 할 수 있습니다(전자상거래법 제17조).
- 단, 다음의 경우 제한될 수 있습니다: 이용자 책임으로 상품이 훼손된 경우, 사용·일부 소비로 가치가 현저히 감소한 경우, 복제 가능한 상품의 포장 훼손 등.

2. 교환·반품 비용
- 상품 하자·오배송: 회사(판매자) 부담
- 단순 변심: 이용자 부담(왕복 배송비)

3. 환불
- 반품 상품 수령·확인 후 영업일 기준 3일 이내 처리하며, 결제수단별로 취소/환급됩니다.
- 피팅 횟수 쿠폰은 현금으로 환급되지 않으며, 유효기간이 지나면 소멸합니다.

4. 구매안전(에스크로) 서비스
- 회사는 선결제 거래의 안전을 위해 결제대금예치(에스크로) 또는 소비자피해보상보험 등 구매안전서비스를 이용합니다.
- 가입 정보: ${COMPANY.escrow}

5. 위탁(배송대행) 상품
- 일부 상품은 위탁(배송대행) 방식으로 공급사가 회원에게 직접 발송하나, 주문·결제·청약철회·교환·환불은 모두 회사가 본 약관 및 관련 법령에 따라 책임집니다.

문의: ${COMPANY.tel} / ${COMPANY.email} 실제 운영 정책·구매안전서비스 가입 후 보완하세요.`,
  },
};

function LegalModal({ docKey, onClose }) {
  if (!docKey) return null;
  const doc = LEGAL_DOCS[docKey];
  if (!doc) return null;
  return (
    <div onClick={onClose}
      style={{ position: "fixed", inset: 0, background: "rgba(15,15,25,.55)", zIndex: 1000, display: "grid", placeItems: "center", padding: 20 }}>
      <div onClick={(e) => e.stopPropagation()}
        style={{ background: "#fff", borderRadius: 16, maxWidth: 640, width: "100%", maxHeight: "82vh", display: "flex", flexDirection: "column", overflow: "hidden", boxShadow: "var(--sh-2)" }}>
        <div className="row" style={{ justifyContent: "space-between", padding: "18px 22px", borderBottom: "1px solid var(--border)" }}>
          <strong style={{ fontSize: 16 }}>{doc.title}</strong>
          <button onClick={onClose} aria-label="닫기"
            style={{ background: "none", border: 0, cursor: "pointer", color: "var(--sub)", display: "flex", padding: 0 }}>
            <Icon name="close" size={20} />
          </button>
        </div>
        <div style={{ padding: "20px 22px", overflowY: "auto", whiteSpace: "pre-wrap", fontSize: 13.5, lineHeight: 1.7, color: "var(--ink)" }}>
          {doc.body}
        </div>
      </div>
    </div>
  );
}

/* ---------- 푸터 ---------- */
function Footer({ go }) {
  const [legalDoc, setLegalDoc] = useState(null);
  return (
    <footer className="ftr">
      <div className="wrap ftr-inner">
        <div>
          <div className="logo" style={{ marginBottom: 14 }}><span className="dot"></span>ibeobwa</div>
          <p className="t-small t-sub" style={{ maxWidth: 260, margin: 0 }}>
            사기 전에, 입어보세요. 가상피팅으로 실패 없는 온라인 쇼핑을 경험하세요.
          </p>
        </div>
        <div><h4>탐색</h4><ul>
          <li><a href="#" onClick={(e) => { e.preventDefault(); go("catalog"); }}>전체 상품</a></li>
          <li><a href="#" onClick={(e) => { e.preventDefault(); go("catalog"); }}>상의 · 하의</a></li>
          <li><a href="#" onClick={(e) => { e.preventDefault(); go("catalog"); }}>원피스 · 아우터</a></li>
          <li><a href="#" onClick={(e) => { e.preventDefault(); go("tryon"); }}>가상피팅</a></li>
        </ul></div>
        <div><h4>고객</h4><ul>
          <li><a href="#">자주 묻는 질문</a></li>
          <li><a href="#">피팅 가이드</a></li>
          <li><a href="#" onClick={(e) => { e.preventDefault(); go("mypage"); }}>내 피팅 기록</a></li>
          <li><a href="#">고객센터</a></li>
        </ul></div>
        <div><h4>회사·약관</h4><ul>
          <li><a href="#">브랜드 소개</a></li>
          <li><a href="#" onClick={(e) => { e.preventDefault(); setLegalDoc("terms"); }}>이용약관</a></li>
          <li><a href="#" onClick={(e) => { e.preventDefault(); setLegalDoc("privacy"); }}><strong>개인정보처리방침</strong></a></li>
          <li><a href="#" onClick={(e) => { e.preventDefault(); setLegalDoc("refund"); }}>청약철회·교환·환불</a></li>
        </ul></div>
      </div>
      <div className="wrap">
        {/* 전자상거래법 사업자정보 표시 */}
        <div className="t-caption t-sub" style={{ lineHeight: 1.85, padding: "22px 0 4px", borderTop: "1px solid var(--border)" }}>
          {COMPANY.name} · 대표 {COMPANY.ceo} · 사업자등록번호 {COMPANY.bizNo} · 통신판매업 신고 {COMPANY.salesNo}<br />
          주소 {COMPANY.addr} · 고객문의 {COMPANY.tel} · {COMPANY.email} · 호스팅 {COMPANY.host}<br />
          개인정보 보호책임자 {COMPANY.privacyOfficer} ({COMPANY.privacyContact})
        </div>
        <div className="ftr-bottom">
          <span>© 2026 ibeobwa. All rights reserved.</span>
          <span>구매안전(에스크로) 서비스로 안전하게 거래하세요.</span>
        </div>
      </div>
      <LegalModal docKey={legalDoc} onClose={() => setLegalDoc(null)} />
    </footer>
  );
}

/* ---------- 토스트 ---------- */
function Toasts({ items }) {
  return (
    <div className="toast-wrap">
      {items.map((t) => (
        <div className="toast" key={t.id}>
          <Icon name="check" size={16} stroke={3} style={{ color: "#A78BFA" }} />{t.msg}
        </div>
      ))}
    </div>
  );
}

Object.assign(window, { Icon, PhImg, Btn, Price, Rating, ProductCard, Check, Chip, Header, Footer, Toasts });

/* ---------- 부위별 핏 카드 (앱 fit_diagram.dart 1:1 이식) ----------
   기장/어깨/소매 + 둘레 여유("허리 +4cm · 적당 (내 60 · 옷 64)")를 테두리 카드로. 헬퍼는 전역 참조. */
function FitDiagram({ p, sizeReco, auth, go }) {
  if (!(sizeReco && sizeReco.available && p && p.sizeChart && auth.height)) return null;
  const rec = String(sizeReco.size || "");
  const entry = Object.entries(p.sizeChart).find(([sz]) => _sizeEq(sz, rec));
  const m = entry ? entry[1] : null;
  if (!m) return null;
  const bottom = _isBottom(p, sizeReco);
  const gk = _garmentKind(p, sizeReco);
  const userLen = gk === "pants" ? auth.legLength : (gk === "top" ? auth.torsoLength : null);
  // ★기장 키 — 맨 '길이'가 '소매길이'에도 매칭돼, 표에 소매길이가 먼저 오면 기장이 소매값을 집던 버그.
  //   소매 키는 제외하고 찾는다. (Flutter fit_diagram 은 우선순위 정확키라 문제 없었음)
  const lenKey = Object.keys(m).find((k) => !/소매/.test(k) && /총장|총기장|총길이|기장|옷길이|길이/.test(k));
  const total = (lenKey != null && m[lenKey] != null && !isNaN(parseFloat(m[lenKey]))) ? parseFloat(m[lenKey]) : null;
  // 소매 키는 '소매' 또는 '소매길이' 등 — 정확히 m["소매"]만 보면 '소매길이' 표기를 놓쳐 소매 행이 안 뜬다.
  const sleeveKey = Object.keys(m).find((k) => /소매/.test(k));
  const sleeve = (!bottom && sleeveKey != null && m[sleeveKey] != null && !isNaN(parseFloat(m[sleeveKey]))) ? parseFloat(m[sleeveKey]) : null;
  // 기장·소매 문구 = 옷 분류(항상) + 내 몸 기준 보조 문구(실측 길이 있을 때만).
  // null 이면(측정 오류 등) 그 행 자체를 숨긴다.
  const hemFeel = total != null ? _lengthHint(total, auth.height, gk, userLen) : null;
  const sleeveFeel = sleeve != null ? _sleeveFit(sleeve, auth.armLength, auth.height) : null;
  // 어깨 — 상의만, 수선 어려운 결정타 (둘레 아니라 직선 너비)
  let shoulderRow = null;
  if (!bottom && m["어깨"] != null) {
    const sf = _shoulderFit(parseFloat(m["어깨"]), auth.shoulder, auth.height, auth.gender);
    if (sf) shoulderRow = { val: parseFloat(m["어깨"]),
      feel: `어깨 ${sf.d >= 0 ? "+" : ""}${sf.d}cm · ${sf.txt}${sf.estimated ? " (추정)" : ""}`,
      color: sf.d <= -2 ? "#E0930B" : sf.d <= 4 ? "#059669" : "var(--primary)" };
  }
  // 둘레 여유 — 상의=가슴 / 하의=허리(+엉덩이). 단면이면 ×2 환산 후 내 둘레와 비교.
  const _ez = (keys, body, label) => {
    if (!body) return null;
    for (const k of keys) { const v = m[k]; if (v != null && !isNaN(parseFloat(v))) { const circ = _toCirc(parseFloat(v), k); return { label, ease: Math.round(circ - body), body, circ: Math.round(circ) }; } }
    return null;
  };
  const eases = [];
  if (!bottom) { const e = _ez(_CHEST_KEYS, auth.chest, "가슴"); if (e) eases.push(e); }
  else {
    const w = _ez(_WAIST_KEYS, auth.waist, "허리"), hp = _ez(_HIP_KEYS, auth.hip, "엉덩이");
    if (w) { eases.push(w); if (hp) eases.push(hp); } else if (hp) eases.push(hp);
  }
  const hasEase = eases.length > 0;
  const missingPart = bottom ? "허리/엉덩이" : "가슴";
  if (!(shoulderRow || hemFeel || sleeveFeel || hasEase)) return null;
  const target = bottom ? 3 : 10;
  const row = (label, value, feel, color) => (
    <div key={label} style={{ display: "flex", alignItems: "center", padding: "4px 0" }}>
      <span style={{ width: 40, fontSize: 12.5, fontWeight: 600, flex: "none" }}>{label}</span>
      <span style={{ fontSize: 13.5, fontWeight: 700, color: "var(--ink)" }}>{value}</span>
      {feel ? <span style={{ marginLeft: 8, fontSize: 12.5, color, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>· {feel}</span> : null}
    </div>
  );
  return (
    <div style={{ padding: 16, background: "#fff", border: "1px solid var(--border)", borderRadius: 14, marginBottom: 12 }}>
      <div style={{ display: "flex", alignItems: "baseline", gap: 6 }}>
        <span style={{ fontSize: 15, fontWeight: 700, color: "var(--ink)" }}>부위별 핏</span>
        <span className="t-small t-sub">· {sizeReco.size_label || sizeReco.size} 기준</span>
      </div>
      <p className="t-small t-sub" style={{ margin: "4px 0 12px" }}>
        {hasEase ? `내 키 ${auth.height}cm · 둘레로 계산한 핏이에요.` : `내 키 ${auth.height}cm 기준 · ${missingPart}둘레 입력 시 여유까지 계산돼요.`}
      </p>
      {shoulderRow && row("어깨", `${Math.round(shoulderRow.val)}cm`, shoulderRow.feel, shoulderRow.color)}
      {hemFeel && row("기장", `${Math.round(total)}cm`, hemFeel, "var(--primary)")}
      {sleeveFeel && row("소매", `${Math.round(sleeve)}cm`, sleeveFeel, "var(--primary)")}
      {eases.map((e) => {
        const tag = e.ease < 0 ? "#E0930B" : e.ease <= target + 2 ? "#059669" : "var(--primary)";
        const feel = e.ease < 0 ? "타이트" : e.ease <= target + 2 ? "적당" : "넉넉";
        return (
          <div key={e.label} style={{ display: "flex", alignItems: "center", padding: "4px 0" }}>
            <span style={{ width: 40, fontSize: 12.5, fontWeight: 600, flex: "none" }}>{e.label}</span>
            <span style={{ fontSize: 13.5, fontWeight: 800, color: tag }}>{e.ease >= 0 ? "+" : ""}{e.ease}cm</span>
            <span style={{ marginLeft: 8, fontSize: 12.5, color: tag, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>· {feel} (내 {e.body} · 옷 {e.circ})</span>
          </div>
        );
      })}
      <p className="t-small t-sub" style={{ margin: "6px 0 0" }}>* 기장·소매는 옷 실측 × 내 키 기준{(auth.legLength || auth.torsoLength || auth.armLength) ? " · 내 실측 길이로 수선까지 가늠" : ""} · 실제 핏은 가상피팅으로 확인하세요.</p>
      {!hasEase && (
        <button type="button" onClick={() => go("mypage", { tab: "profile" })} style={{ marginTop: 10, width: "100%", padding: "11px", borderRadius: 8, border: "1px solid var(--primary)", background: "var(--accent-soft)", color: "var(--primary)", fontWeight: 700, fontSize: 12.5, cursor: "pointer", fontFamily: "inherit" }}>
          📏 내 {missingPart}둘레 넣고 정확한 추천 켜기
        </button>
      )}
    </div>
  );
}

/* ---------- 사이즈 · 체형 분석 패널 (상세·입어보기 공용) ----------
   앱 detail_screen 의 SizeAnalysisPanel 과 동일 내용. 계산 헬퍼(_bmiInfo·_colRank·_rowEase 등)는
   screens-tryon.jsx 의 top-level 함수를 그대로 참조한다(Babel standalone 은 스크립트 간 전역 공유). */
function SizeAnalysisPanel({ p, sizeReco, auth, go }) {
  return (
    <div>
      <p className="t-small t-sub" style={{ margin: "0 0 8px", fontWeight: 600 }}>사이즈 · 체형 분석</p>
      <div className="card" style={{ padding: 14 }}>
              {/* 내 체형 */}
              <div style={{ fontSize: 13, marginBottom: 10 }}>
                <span style={{ fontWeight: 600 }}>내 체형</span>{" · "}
                {(auth.height || auth.weight || auth.chest || auth.waist || auth.shoulder)
                  ? `${auth.gender && /^(m|남)/i.test(auth.gender) ? "남성 " : auth.gender && /^(f|w|여)/i.test(auth.gender) ? "여성 " : ""}${auth.height ? auth.height + "cm" : ""}${auth.weight ? " · " + auth.weight + "kg" : ""}${auth.chest ? " · 가슴 " + auth.chest : ""}${auth.waist ? " · 허리 " + auth.waist : ""}${auth.shoulder ? " · 어깨 " + auth.shoulder : ""}`
                  : <span style={{ color: "var(--sub)" }}>마이 &gt; 내 정보에서 키·몸무게·둘레 등록 시 정확도↑</span>}
                {(() => { const bi = _bmiInfo(auth.height, auth.weight); return bi ? <span style={{ color: "var(--sub)" }}>{` · BMI ${bi.bmi} (${bi.label})`}</span> : null; })()}
              </div>

              {/* 추천 사이즈 */}
              {sizeReco && sizeReco.available && (
                <div style={{ background: "#EEF3FF", borderRadius: 10, padding: 12, marginBottom: 12 }}>
                  <div style={{ fontSize: 12, color: "var(--sub)" }}>이 상품 추천 사이즈</div>
                  <div style={{ fontSize: 20, fontWeight: 700, color: "var(--primary)", margin: "2px 0 4px" }}>{sizeReco.size_label || sizeReco.size}</div>
                  {/* (착용 선호 토글 삭제 — 사용자 요청. 추천은 정사이즈 기준 단일 표시, 스마트앱과 파리티) */}
                  {/* 옷 재단 안내 배지 (오버핏/슬림 컷) */}
                  {sizeReco.cut_note && (
                    <div style={{ fontSize: 12, color: "#7C3AED", background: "#F3EEFF", borderRadius: 8, padding: "5px 9px", marginBottom: 6 }}>🧵 {sizeReco.cut_note}</div>
                  )}
                  {sizeReco.reason && <div style={{ fontSize: 12, color: "var(--sub)" }}>{sizeReco.reason}</div>}
                  {/* 부위가 서로 다른 사이즈를 가리킬 때만 범위 + 부위별 추천을 보여준다(끼임 방지로 큰 쪽 선택) */}
                  {sizeReco.size_range && sizeReco.size_range.includes("~") && (
                    <div style={{ fontSize: 12, marginTop: 5 }}>
                      <span style={{ color: "var(--primary)", fontWeight: 700 }}>부위별 추천 {sizeReco.size_range}</span>
                      {sizeReco.parts && sizeReco.parts.length > 1 && (
                        <span style={{ color: "var(--sub)" }}>
                          {" · "}{sizeReco.parts.map((pt) => `${pt.part} ${pt.size}(${pt.ease >= 0 ? "+" : ""}${pt.ease})`).join(" / ")}
                        </span>
                      )}
                    </div>
                  )}
                  {/* 평소 사이즈 앵커링 — "평소 M보다 작게 나와요" (평소 사이즈 입력 시) */}
                  {sizeReco.anchor_note && (
                    <div style={{ fontSize: 12.5, marginTop: 6, fontWeight: 600, color: "var(--primary-dark, var(--primary))" }}>👕 {sizeReco.anchor_note}</div>
                  )}
                  {/* 크라우드 핏 — 비슷한 체형 구매자 후기 기반 (후기 3개 이상) */}
                  {sizeReco.crowd_note && (
                    <div style={{ fontSize: 12.5, marginTop: 5, fontWeight: 600, color: "#7C3AED" }}>👥 {sizeReco.crowd_note}</div>
                  )}
                  {sizeReco.fit_note && <div style={{ fontSize: 13, marginTop: 6, lineHeight: 1.5 }}>{sizeReco.fit_note}</div>}
                  {sizeReco.confidence && <div style={{ fontSize: 11, color: "var(--sub)", marginTop: 4 }}>신뢰도 {sizeReco.confidence}</div>}
                </div>
              )}
              {sizeReco && !sizeReco.available && (
                <p style={{ fontSize: 12.5, color: "var(--sub)", margin: "0 0 12px", lineHeight: 1.5 }}>{sizeReco.message}</p>
              )}

              {/* 부위별 핏 — 앱 FitDiagram 카드(둘레 여유 "내 60 · 옷 64" 포함) */}
              <FitDiagram p={p} sizeReco={sizeReco} auth={auth} go={go} />

              {/* 실측 사이즈표 (추천 사이즈 행 강조) */}
              {(() => {
                const rows = (p && p.sizeChart) ? Object.entries(p.sizeChart) : [];
                if (!rows.length) return <p style={{ fontSize: 12, color: "var(--sub)" }}>이 상품은 실측 사이즈표가 없어요. (체형 기반 추천만 제공)</p>;
                const cols = [...new Set(rows.flatMap(([, m]) => Object.keys(m || {})))]
                  .sort((a, b) => _colRank(a) - _colRank(b));   // 총기장 → 어깨 → 가슴 … 일정 순서
                const rec = sizeReco && sizeReco.size ? String(sizeReco.size) : null;
                const isBottom = _isBottom(p, sizeReco);
                // 표가 실제로 가진 치수에 맞춰 비교 부위를 고른다. (하의: 허리 우선, 없으면 엉덩이)
                let body, part, easeKeys;
                if (!isBottom) { body = auth.chest; part = "가슴"; easeKeys = _CHEST_KEYS; }
                else if (cols.some((c) => /허리/.test(c))) { body = auth.waist; part = "허리"; easeKeys = _WAIST_KEYS; }
                else if (cols.some((c) => /엉덩이|힙/.test(c))) { body = auth.hip; part = "엉덩이"; easeKeys = _HIP_KEYS; }
                else { body = auth.waist; part = "허리"; easeKeys = _WAIST_KEYS; }
                return (
                  <>
                    <table style={{ width: "100%", borderCollapse: "collapse", fontSize: 11.5 }}>
                      <thead>
                        <tr style={{ color: "var(--sub)", borderBottom: "1px solid var(--border)" }}>
                          <th style={{ textAlign: "left", padding: "4px 3px" }}>사이즈</th>
                          {cols.map((c) => <th key={c} style={{ textAlign: "right", padding: "4px 3px" }}>{_dispColName(c)}</th>)}
                          {body ? <th style={{ textAlign: "right", padding: "4px 3px", color: "var(--primary)" }}>{part}여유</th> : null}
                        </tr>
                      </thead>
                      <tbody>
                        {rows.map(([sz, m]) => {
                          const on = rec && _sizeEq(sz, rec);
                          const ease = body ? _rowEase(m, body, easeKeys, isBottom) : null;
                          return (
                            <tr key={sz} style={{ background: on ? "#EEF3FF" : "transparent", fontWeight: on ? 700 : 400 }}>
                              <td style={{ padding: "4px 3px" }}>{sz}{on ? " ◀ 추천" : ""}</td>
                              {cols.map((c) => <td key={c} style={{ textAlign: "right", padding: "4px 3px" }}>{_dispVal(c, m && m[c])}</td>)}
                              {body ? <td style={{ textAlign: "right", padding: "4px 3px", fontWeight: 600, color: ease == null ? "var(--sub)" : ease < 0 ? "#DC2626" : ease <= 12 ? "#059669" : "#D97706" }}>{ease == null ? "-" : (ease >= 0 ? "+" : "") + ease}</td> : null}
                            </tr>
                          );
                        })}
                      </tbody>
                    </table>
                    {body
                      ? <p style={{ fontSize: 10.5, color: "var(--sub)", marginTop: 4, lineHeight: 1.5 }}>{part}여유 = 옷 {part}둘레 − 내 {part}({body}cm) · <span style={{ color: "#059669" }}>초록=적당</span> <span style={{ color: "#D97706" }}>주황=넉넉</span> <span style={{ color: "#DC2626" }}>빨강=작음</span></p>
                      : <button type="button" onClick={() => go("mypage", { tab: "profile" })}
                          style={{ marginTop: 6, width: "100%", padding: "9px 10px", borderRadius: 8, border: "1px solid var(--primary)", background: "var(--accent-soft)", color: "var(--primary)", fontWeight: 700, fontSize: 11.5, cursor: "pointer", fontFamily: "inherit" }}>
                          📏 내 {part}둘레 넣고 <u>정확한 사이즈 추천</u> 켜기 →
                        </button>}
                  </>
                );
              })()}
              <p style={{ fontSize: 11, color: "var(--sub)", marginTop: 8, lineHeight: 1.5 }}>단위 cm · 가슴·허리·엉덩이·밑단은 <b>둘레</b>로 표기(옷 단면이면 ×2 환산) · 어깨·기장(총장)·소매는 길이 그대로. 실제 핏은 가상피팅으로 확인하세요.</p>
      </div>
    </div>
  );
}
Object.assign(window, { SizeAnalysisPanel, FitDiagram });
