@primestyleai/tryon 5.10.102 → 5.10.104

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -10078,12 +10078,13 @@ function parseNum(s) {
10078
10078
  const n2 = parseFloat(s.replace(/[^\d.]/g, ""));
10079
10079
  return isNaN(n2) ? 0 : n2;
10080
10080
  }
10081
- function computeFit(userValue, chartRange) {
10081
+ function computeFit(userValue, chartRange, unit) {
10082
10082
  const { min: rMin, max: rMax } = parseRange(chartRange);
10083
10083
  if (rMin === 0 && rMax === 0) return "good";
10084
10084
  const range = rMax - rMin;
10085
10085
  const threshold = range > 0 ? range * 0.5 : rMin * 0.05 || 3;
10086
- if (userValue >= rMin && userValue <= rMax) return "good";
10086
+ const perfectTol = unit === "cm" ? 2.54 : unit === "mm" ? 25.4 : 1;
10087
+ if (userValue > rMin - perfectTol && userValue < rMax + perfectTol) return "good";
10087
10088
  if (userValue < rMin) {
10088
10089
  const diff2 = rMin - userValue;
10089
10090
  if (diff2 > threshold * 2) return "too-loose";
@@ -10107,12 +10108,13 @@ const SKIP_AREAS_FOR_FIT = /* @__PURE__ */ new Set([
10107
10108
  "altezza",
10108
10109
  "estatura"
10109
10110
  ]);
10110
- function buildFitInfo(matchDetails, poseLines) {
10111
+ function buildFitInfo(matchDetails, poseLines, unit) {
10111
10112
  return matchDetails.filter((m2) => !SKIP_AREAS_FOR_FIT.has(m2.measurement.toLowerCase().replace(/\s*\(.*?\)\s*/g, "").trim())).map((m2) => {
10112
10113
  const userNum = parseNum(m2.userValue);
10113
- const fit = computeFit(userNum, m2.chartRange);
10114
+ const fit = computeFit(userNum, m2.chartRange, unit);
10114
10115
  const info = {
10115
10116
  area: m2.measurement,
10117
+ section: m2.section || void 0,
10116
10118
  fit,
10117
10119
  userValue: userNum || void 0,
10118
10120
  garmentRange: m2.chartRange || void 0
@@ -10586,6 +10588,45 @@ function getUnitLabel(unit) {
10586
10588
  if (unit === "mm") return "mm";
10587
10589
  return "";
10588
10590
  }
10591
+ const cache = /* @__PURE__ */ new Map();
10592
+ function scoreLandmarks(lm) {
10593
+ if (!lm) return 0;
10594
+ let joints = 0;
10595
+ for (const [k2, v2] of Object.entries(lm)) {
10596
+ if (k2 === "imageWidth" || k2 === "imageHeight") continue;
10597
+ if (v2 && typeof v2 === "object" && typeof v2.x === "number") joints++;
10598
+ }
10599
+ let score = joints * 10;
10600
+ if (lm.nose) score += 50;
10601
+ if (lm.leftAnkle && lm.rightAnkle) score += 5;
10602
+ return score;
10603
+ }
10604
+ async function scoreImage(url) {
10605
+ try {
10606
+ const lm = await detectBodyLandmarks(url);
10607
+ return scoreLandmarks(lm);
10608
+ } catch {
10609
+ return 0;
10610
+ }
10611
+ }
10612
+ async function pickBestGarmentImage(images) {
10613
+ if (!images || !images.length) return null;
10614
+ if (images.length === 1) return images[0];
10615
+ const cacheKey = images.join("|");
10616
+ const cached = cache.get(cacheKey);
10617
+ if (cached) return cached;
10618
+ const t0 = Date.now();
10619
+ const scored = await Promise.all(images.map(async (url) => ({
10620
+ url,
10621
+ score: await scoreImage(url)
10622
+ })));
10623
+ scored.sort((a, b) => b.score - a.score);
10624
+ const best = (scored[0]?.score ?? 0) > 0 ? scored[0].url : images[0];
10625
+ cache.set(cacheKey, best);
10626
+ console.log(`[ps-sdk:garment-pick] ${Date.now() - t0}ms — chose ${images.indexOf(best)}/${images.length}`);
10627
+ for (const s of scored) console.log(`[ps-sdk:garment-pick] ${s.score.toString().padStart(4, " ")} ${s.url}`);
10628
+ return best;
10629
+ }
10589
10630
  function cx(base, override) {
10590
10631
  return override ? `${base} ${override}` : base;
10591
10632
  }
@@ -11328,6 +11369,37 @@ const STYLES$1 = `
11328
11369
  border: 1px solid rgba(33,84,239,0.25); border-radius: 2vw;
11329
11370
  padding: 0.1vw 0.5vw;
11330
11371
  }
11372
+ .ps-tryon-sr-card-v2-rec-pill {
11373
+ align-self: flex-start;
11374
+ font-size: 0.55vw; font-weight: 700; color: var(--ps-accent);
11375
+ text-transform: uppercase; letter-spacing: 0.08em;
11376
+ background: rgba(33, 84, 239, 0.10);
11377
+ border: 1px solid rgba(33, 84, 239, 0.18);
11378
+ border-radius: 2vw;
11379
+ padding: 0.18vw 0.6vw;
11380
+ margin-top: 0.3vw;
11381
+ }
11382
+ .ps-tryon-sr-card-v2-rec-pill.is-overridden {
11383
+ color: #b45309;
11384
+ background: rgba(180, 83, 9, 0.10);
11385
+ border-color: rgba(180, 83, 9, 0.25);
11386
+ }
11387
+ .ps-tryon-sr-card-v2-view {
11388
+ align-self: center;
11389
+ margin-top: 0.4vw;
11390
+ font-size: 0.62vw; font-weight: 600;
11391
+ color: var(--ps-accent);
11392
+ text-transform: uppercase; letter-spacing: 0.06em;
11393
+ display: inline-flex; align-items: center; justify-content: center; gap: 0.2vw;
11394
+ transition: gap 0.2s ease;
11395
+ }
11396
+ .ps-tryon-sr-card-v2:hover .ps-tryon-sr-card-v2-view {
11397
+ gap: 0.4vw;
11398
+ }
11399
+ .ps-tryon-sr-card-v2-view > span {
11400
+ font-size: 0.85vw; line-height: 1; color: var(--ps-accent);
11401
+ transform: translateY(-0.05vw);
11402
+ }
11331
11403
  .ps-tryon-sr-card-v2-img { display: none; }
11332
11404
  .ps-tryon-sr-card-v2-icon {
11333
11405
  position: absolute; bottom: 0.35vw; right: 0.45vw;
@@ -11391,24 +11463,165 @@ const STYLES$1 = `
11391
11463
  filter: blur(8px) brightness(0.5); transform: scale(1.05);
11392
11464
  transition: filter 0.5s ease, transform 0.5s ease;
11393
11465
  }
11394
- .ps-tryon-v2-processing-label {
11395
- position: absolute; bottom: 1vw; left: 50%; transform: translateX(-50%);
11396
- z-index: 5; font-size: 0.7vw; font-weight: 600;
11397
- color: #fff; letter-spacing: 0.05em;
11398
- background: rgba(0,0,0,0.72); backdrop-filter: blur(10px);
11399
- padding: 0.6vw 0.9vw; border-radius: 0.6vw;
11400
- display: flex; flex-direction: column; align-items: center; gap: 0.5vw;
11401
- min-width: 14vw;
11402
- box-shadow: 0 0.4vw 1.5vw rgba(0,0,0,0.35);
11466
+ /* ── Try-on generation badge (left-side overlay during VTO) ── */
11467
+ .ps-tryon-badge {
11468
+ position: absolute; bottom: 16px; left: 16px; right: 16px;
11469
+ z-index: 5;
11470
+ background: #ffffff;
11471
+ border-radius: 14px;
11472
+ padding: 14px 18px;
11473
+ max-width: 420px;
11474
+ box-shadow: 0 8px 28px rgba(20, 30, 60, 0.14), 0 2px 6px rgba(20, 30, 60, 0.08);
11475
+ display: flex; flex-direction: column; gap: 10px;
11476
+ font-family: inherit;
11477
+ pointer-events: none;
11478
+ }
11479
+ .ps-tryon-badge-row {
11480
+ display: flex; align-items: center; gap: 12px;
11403
11481
  }
11404
- .ps-tryon-v2-processing-label > span:first-child {
11405
- animation: ps-loading-pulse 2s ease-in-out infinite;
11482
+ .ps-tryon-badge-spinner {
11483
+ display: inline-flex; flex-shrink: 0;
11484
+ animation: ps-tryon-badge-spin 1s linear infinite;
11485
+ }
11486
+ .ps-tryon-badge-title {
11487
+ flex: 1; min-width: 0;
11488
+ font-size: 15px; font-weight: 600; color: #0f172a;
11489
+ white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
11490
+ }
11491
+ .ps-tryon-badge-pct {
11492
+ flex-shrink: 0;
11493
+ background: linear-gradient(135deg, var(--ps-accent) 0%, var(--ps-accent-hover) 100%);
11494
+ color: #fff; font-size: 12px; font-weight: 700;
11495
+ padding: 4px 11px; border-radius: 999px;
11496
+ letter-spacing: 0.02em;
11497
+ }
11498
+ .ps-tryon-badge-bar {
11499
+ height: 5px;
11500
+ background: #eef2f8;
11501
+ border-radius: 999px; overflow: hidden;
11502
+ }
11503
+ .ps-tryon-badge-bar-fill {
11504
+ height: 100%;
11505
+ background: linear-gradient(90deg, var(--ps-accent) 0%, var(--ps-accent-hover) 100%);
11506
+ border-radius: 999px;
11507
+ transition: width 0.3s ease;
11508
+ }
11509
+ .ps-tryon-badge-foot {
11510
+ display: flex; align-items: center; justify-content: space-between; gap: 10px;
11511
+ font-size: 12px; color: #6b7280;
11512
+ }
11513
+ .ps-tryon-badge-status {
11514
+ display: inline-flex; align-items: center; gap: 6px;
11515
+ font-weight: 500;
11516
+ white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
11517
+ }
11518
+ .ps-tryon-badge-status-icon {
11519
+ display: inline-flex; flex-shrink: 0; color: var(--ps-accent);
11520
+ }
11521
+ .ps-tryon-badge-eta {
11522
+ display: inline-flex; align-items: center; gap: 5px;
11523
+ font-weight: 500; flex-shrink: 0;
11524
+ }
11525
+ @keyframes ps-tryon-badge-spin {
11526
+ to { transform: rotate(360deg); }
11527
+ }
11528
+
11529
+ /* ── Product photo strip (single-garment, below the size card) ──
11530
+ Three thumbnails per slide, evenly spaced, auto-advances. Lives at
11531
+ the bottom of the right panel as decoration / entertainment. */
11532
+ .ps-tryon-photo-strip {
11533
+ margin-top: 1vw;
11534
+ display: flex; flex-direction: column; gap: 0.6vw;
11535
+ }
11536
+ .ps-tryon-photo-strip-head {
11537
+ display: flex; align-items: center; justify-content: space-between;
11538
+ }
11539
+ .ps-tryon-photo-strip-badge {
11540
+ color: var(--ps-accent);
11541
+ font-size: 0.7vw; font-weight: 600;
11542
+ display: inline-flex; align-items: center; gap: 0.3vw;
11543
+ letter-spacing: 0.04em; text-transform: uppercase;
11544
+ }
11545
+ .ps-tryon-photo-strip-row {
11546
+ display: grid;
11547
+ grid-template-columns: repeat(3, 1fr);
11548
+ gap: 0.6vw;
11549
+ animation: ps-tryon-photo-strip-fade 0.5s ease;
11550
+ }
11551
+ .ps-tryon-photo-strip-cell {
11552
+ position: relative;
11553
+ aspect-ratio: 3 / 4;
11554
+ overflow: hidden;
11555
+ border-radius: 0.5vw;
11556
+ background: #f3f5f9;
11557
+ box-shadow: 0 0.15vw 0.5vw rgba(20, 30, 60, 0.08);
11558
+ transition: transform 0.25s ease;
11559
+ }
11560
+ .ps-tryon-photo-strip-cell:hover {
11561
+ transform: translateY(-0.15vw);
11562
+ }
11563
+ .ps-tryon-photo-strip-cell > img {
11564
+ width: 100%; height: 100%;
11565
+ object-fit: cover;
11566
+ user-select: none;
11567
+ pointer-events: none;
11568
+ }
11569
+ .ps-tryon-photo-strip-dots {
11570
+ display: flex; justify-content: center; align-items: center; gap: 0.3vw;
11571
+ padding-top: 0.2vw;
11572
+ }
11573
+ .ps-tryon-photo-strip-dot {
11574
+ width: 0.3vw; height: 0.3vw; min-width: 4px; min-height: 4px;
11575
+ border-radius: 999px;
11576
+ background: #d6dbe4;
11577
+ transition: background-color 0.3s ease, width 0.3s ease;
11578
+ }
11579
+ .ps-tryon-photo-strip-dot.is-active {
11580
+ background: var(--ps-accent);
11581
+ width: 0.9vw; min-width: 14px;
11582
+ }
11583
+ @keyframes ps-tryon-photo-strip-fade {
11584
+ from { opacity: 0; transform: translateY(0.3vw); }
11585
+ to { opacity: 1; transform: translateY(0); }
11586
+ }
11587
+
11588
+ /* ── No-fit empty state (single-garment, sizing match% < 50%) ── */
11589
+ .ps-tryon-nofit {
11590
+ background: #ffffff;
11591
+ border: 1px solid #eef2f8;
11592
+ border-radius: 1vw;
11593
+ padding: 3vw 2.4vw 2.6vw;
11594
+ text-align: center;
11595
+ display: flex; flex-direction: column; align-items: center;
11596
+ box-shadow: 0 0.4vw 1.2vw rgba(20, 30, 60, 0.05);
11597
+ }
11598
+ .ps-tryon-nofit-icon {
11599
+ width: 4.4vw; height: 4.4vw; min-width: 56px; min-height: 56px;
11600
+ border-radius: 50%;
11601
+ background: #fef2f2;
11602
+ color: #dc2626;
11603
+ display: flex; align-items: center; justify-content: center;
11604
+ margin-bottom: 1.2vw;
11605
+ }
11606
+ .ps-tryon-nofit-icon svg { width: 50%; height: 50%; }
11607
+ .ps-tryon-nofit-title {
11608
+ font-size: 1.15vw; font-weight: 700; color: #0f172a;
11609
+ margin: 0 0 0.7vw;
11610
+ line-height: 1.3;
11611
+ letter-spacing: -0.01em;
11612
+ }
11613
+ .ps-tryon-nofit-sub {
11614
+ font-size: 0.78vw; color: #6b7280;
11615
+ margin: 0 0 2vw;
11616
+ max-width: 22vw;
11617
+ line-height: 1.6;
11618
+ }
11619
+ .ps-tryon-nofit-actions {
11620
+ display: flex; align-items: center; justify-content: space-between;
11621
+ width: 100%; gap: 1vw;
11622
+ padding-top: 1.2vw;
11623
+ border-top: 1px solid #f1f4fa;
11406
11624
  }
11407
- .ps-tryon-v2-processing-label .ps-tryon-progress-ring-track { stroke: rgba(255,255,255,0.18); }
11408
- .ps-tryon-v2-processing-label .ps-tryon-progress-ring-fill { stroke: var(--ps-accent-light); }
11409
- .ps-tryon-v2-processing-label .ps-tryon-progress-eta { color: #fff; }
11410
- .ps-tryon-v2-processing-label .ps-tryon-progress-bar-wrap { background: rgba(255,255,255,0.18); }
11411
- .ps-tryon-v2-processing-label .ps-tryon-progress-pct { color: var(--ps-accent-light); }
11412
11625
 
11413
11626
  /* "I don't know" link */
11414
11627
  .ps-tryon-v2-dontknow {
@@ -18741,133 +18954,6 @@ function MobileBottomTabs({ mode, onSwitchToManual, onSwitchToScan, t: t2 }) {
18741
18954
  )
18742
18955
  ] });
18743
18956
  }
18744
- const TARGET_SECONDS = 22;
18745
- const RING_RADIUS$1 = 38;
18746
- const RING_CIRC = 2 * Math.PI * RING_RADIUS$1;
18747
- function EngagingTryOnView({
18748
- previewUrl,
18749
- productMaterial,
18750
- productDescription,
18751
- onCancel,
18752
- variant = "split",
18753
- t: t2
18754
- }) {
18755
- const startRef = reactExports.useRef(Date.now());
18756
- const ringRef = reactExports.useRef(null);
18757
- const pctRef = reactExports.useRef(null);
18758
- const statusRef = reactExports.useRef(null);
18759
- const statuses = [
18760
- t2("Preparing your image…"),
18761
- t2("Mapping body landmarks…"),
18762
- t2("Rendering the garment…"),
18763
- t2("Refining drape and shadows…"),
18764
- t2("Almost done — finalizing…")
18765
- ];
18766
- reactExports.useEffect(() => {
18767
- startRef.current = Date.now();
18768
- const id2 = setInterval(() => {
18769
- const elapsed = (Date.now() - startRef.current) / 1e3;
18770
- const pct = Math.min(95, elapsed / TARGET_SECONDS * 100);
18771
- const val = Math.round(pct);
18772
- if (pctRef.current) pctRef.current.textContent = `${val}%`;
18773
- if (ringRef.current) ringRef.current.style.strokeDashoffset = String(RING_CIRC * (1 - pct / 100));
18774
- if (statusRef.current) {
18775
- const stepIdx = Math.min(statuses.length - 1, Math.floor(elapsed / TARGET_SECONDS * statuses.length));
18776
- const desired = statuses[stepIdx];
18777
- if (statusRef.current.textContent !== desired) statusRef.current.textContent = desired;
18778
- }
18779
- }, 200);
18780
- return () => clearInterval(id2);
18781
- }, []);
18782
- const aiFact = t2("Our model is analyzing 150+ body landmarks for the perfect fit");
18783
- const styleTips = [
18784
- t2("Match your belt to your shoes — never your pants"),
18785
- t2("Leave the bottom button of a suit jacket undone"),
18786
- t2("Cuff a pocket square so it peeks 1–2 cm above the pocket"),
18787
- t2("Roll sleeves twice for a relaxed, intentional finish"),
18788
- t2("A tie tip should land at the middle of your belt buckle"),
18789
- t2("Cufflinks should sit half an inch past the jacket sleeve")
18790
- ];
18791
- const [tipIdx, setTipIdx] = reactExports.useState(0);
18792
- reactExports.useEffect(() => {
18793
- const s = setInterval(() => setTipIdx((i) => (i + 1) % styleTips.length), 5e3);
18794
- return () => clearInterval(s);
18795
- }, []);
18796
- const garmentLine = productMaterial?.trim() || (productDescription?.trim() ? productDescription.trim().slice(0, 90) + (productDescription.trim().length > 90 ? "…" : "") : null) || t2("Crafted with quality materials for everyday comfort");
18797
- const Panel = /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-tryon-processing-v2-panel", children: [
18798
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-tryon-processing-v2-head", children: [
18799
- /* @__PURE__ */ jsxRuntimeExports.jsx("h3", { className: "ps-tryon-processing-v2-title", children: t2("Generating Your Look") }),
18800
- /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "ps-tryon-processing-v2-sub", children: t2("Our AI is precisely mapping the garment to your unique proportions.") })
18801
- ] }),
18802
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-tryon-processing-v2-ring-wrap", children: [
18803
- /* @__PURE__ */ jsxRuntimeExports.jsxs("svg", { viewBox: "0 0 96 96", width: "120", height: "120", "aria-hidden": "true", children: [
18804
- /* @__PURE__ */ jsxRuntimeExports.jsx("circle", { cx: "48", cy: "48", r: RING_RADIUS$1, className: "ps-tryon-progress-ring-track" }),
18805
- /* @__PURE__ */ jsxRuntimeExports.jsx(
18806
- "circle",
18807
- {
18808
- ref: ringRef,
18809
- cx: "48",
18810
- cy: "48",
18811
- r: RING_RADIUS$1,
18812
- className: "ps-tryon-progress-ring-fill",
18813
- strokeDasharray: RING_CIRC,
18814
- strokeDashoffset: RING_CIRC
18815
- }
18816
- )
18817
- ] }),
18818
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-tryon-processing-v2-ring-text", children: /* @__PURE__ */ jsxRuntimeExports.jsx("span", { ref: pctRef, className: "ps-tryon-processing-v2-pct", children: "0%" }) })
18819
- ] }),
18820
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { ref: statusRef, className: "ps-tryon-processing-v2-status", children: statuses[0] }),
18821
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-tryon-processing-v2-sep" }),
18822
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
18823
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-tryon-processing-v2-section-label", children: t2("WHILE YOU WAIT") }),
18824
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-tryon-processing-v2-cards", children: [
18825
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-tryon-processing-v2-card", children: [
18826
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-tryon-processing-v2-card-icon ps-style", children: /* @__PURE__ */ jsxRuntimeExports.jsx("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M9 18h6M10 22h4M12 2a7 7 0 0 0-4 12.7c1 .9 1 1.8 1 2.3v1h6v-1c0-.5 0-1.4 1-2.3A7 7 0 0 0 12 2z" }) }) }),
18827
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-tryon-processing-v2-card-text", children: [
18828
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-tryon-processing-v2-card-head", children: t2("Style Tip") }),
18829
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-tryon-processing-v2-card-body", children: styleTips[tipIdx] }, tipIdx)
18830
- ] })
18831
- ] }),
18832
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-tryon-processing-v2-card", children: [
18833
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-tryon-processing-v2-card-icon ps-spotlight", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
18834
- /* @__PURE__ */ jsxRuntimeExports.jsx("circle", { cx: "12", cy: "12", r: "10" }),
18835
- /* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: "12", y1: "8", x2: "12", y2: "12" }),
18836
- /* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: "12", y1: "16", x2: "12.01", y2: "16" })
18837
- ] }) }),
18838
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-tryon-processing-v2-card-text", children: [
18839
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-tryon-processing-v2-card-head", children: t2("Garment Spotlight") }),
18840
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-tryon-processing-v2-card-body", children: garmentLine })
18841
- ] })
18842
- ] }),
18843
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-tryon-processing-v2-card", children: [
18844
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-tryon-processing-v2-card-icon ps-fact", children: /* @__PURE__ */ jsxRuntimeExports.jsx("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M12 2v6M12 16v6M4.93 4.93l4.24 4.24M14.83 14.83l4.24 4.24M2 12h6M16 12h6M4.93 19.07l4.24-4.24M14.83 9.17l4.24-4.24" }) }) }),
18845
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-tryon-processing-v2-card-text", children: [
18846
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-tryon-processing-v2-card-head", children: t2("AI Fact") }),
18847
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-tryon-processing-v2-card-body", children: aiFact })
18848
- ] })
18849
- ] })
18850
- ] })
18851
- ] }),
18852
- onCancel && /* @__PURE__ */ jsxRuntimeExports.jsx("button", { type: "button", className: "ps-tryon-processing-v2-cancel", onClick: onCancel, children: t2("Cancel Generation") })
18853
- ] });
18854
- if (variant === "panel-only") return Panel;
18855
- return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-tryon-processing-v2", children: [
18856
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-tryon-processing-v2-image", children: [
18857
- previewUrl && /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
18858
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-tryon-processing-blur", style: { backgroundImage: `url(${previewUrl})` } }),
18859
- /* @__PURE__ */ jsxRuntimeExports.jsx("img", { src: previewUrl, alt: t2("Your photo"), className: "ps-tryon-processing-model" })
18860
- ] }),
18861
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-tryon-scan-line" }),
18862
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-tryon-scan-overlay" }),
18863
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-tryon-processing-v2-badge", children: [
18864
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "ps-tryon-processing-v2-badge-dot", "aria-hidden": "true" }),
18865
- t2("ANALYZING BODY MAP")
18866
- ] })
18867
- ] }),
18868
- Panel
18869
- ] });
18870
- }
18871
18957
  const SKELETON_CONNECTIONS$1 = [
18872
18958
  ["leftShoulder", "rightShoulder"],
18873
18959
  ["leftShoulder", "leftElbow"],
@@ -18953,30 +19039,6 @@ function MobileScanningView({
18953
19039
  onSwitchToManual,
18954
19040
  t: t2
18955
19041
  }) {
18956
- if (tryOnProcessing) {
18957
- return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-msc-root", children: [
18958
- /* @__PURE__ */ jsxRuntimeExports.jsx(
18959
- EngagingTryOnView,
18960
- {
18961
- previewUrl,
18962
- productMaterial,
18963
- productDescription,
18964
- onCancel: onCancelTryOn,
18965
- t: t2
18966
- }
18967
- ),
18968
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-bpm-bottom", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
18969
- MobileBottomTabs,
18970
- {
18971
- mode: "scan",
18972
- onSwitchToManual,
18973
- onSwitchToScan: () => {
18974
- },
18975
- t: t2
18976
- }
18977
- ) })
18978
- ] });
18979
- }
18980
19042
  const displayImage = previewUrl || productImage || "";
18981
19043
  const isPhotoMode = !!previewUrl;
18982
19044
  const stages = isPhotoMode ? [
@@ -18996,11 +19058,18 @@ function MobileScanningView({
18996
19058
  const img = e.currentTarget;
18997
19059
  setDims({ w: img.naturalWidth || img.offsetWidth, h: img.naturalHeight || img.offsetHeight });
18998
19060
  };
19061
+ const TOTAL_MS = 6e3;
19062
+ const LAST_HOLD_MS = 1e3;
19063
+ const startRef = reactExports.useRef(Date.now());
18999
19064
  const [stageIdx, setStageIdx] = reactExports.useState(0);
19000
19065
  reactExports.useEffect(() => {
19066
+ const stepMs = (TOTAL_MS - LAST_HOLD_MS) / Math.max(1, stages.length - 1);
19001
19067
  const id2 = setInterval(() => {
19002
- setStageIdx((i) => (i + 1) % stages.length);
19003
- }, 1500);
19068
+ const elapsed = Date.now() - startRef.current;
19069
+ const idx = Math.min(stages.length - 1, Math.floor(elapsed / stepMs));
19070
+ setStageIdx((prev) => prev === idx ? prev : idx);
19071
+ if (idx >= stages.length - 1) clearInterval(id2);
19072
+ }, 100);
19004
19073
  return () => clearInterval(id2);
19005
19074
  }, [stages.length]);
19006
19075
  reactExports.useEffect(() => {
@@ -19235,6 +19304,132 @@ function MultiSectionMobile({
19235
19304
  sizeGuide ? null : null
19236
19305
  ] });
19237
19306
  }
19307
+ const TARGET_SECONDS = 22;
19308
+ function TryOnGenerationBadge({
19309
+ tryOnStartedAt,
19310
+ t: t2
19311
+ }) {
19312
+ const [, force] = reactExports.useState(0);
19313
+ reactExports.useEffect(() => {
19314
+ if (tryOnStartedAt == null) return;
19315
+ const id2 = setInterval(() => force((v2) => v2 + 1), 200);
19316
+ return () => clearInterval(id2);
19317
+ }, [tryOnStartedAt]);
19318
+ if (tryOnStartedAt == null) return null;
19319
+ const elapsed = (Date.now() - tryOnStartedAt) / 1e3;
19320
+ const pct = Math.min(95, elapsed / TARGET_SECONDS * 100);
19321
+ const pctRounded = Math.round(pct);
19322
+ const remaining = Math.max(0, TARGET_SECONDS - Math.floor(elapsed));
19323
+ const etaText = elapsed >= TARGET_SECONDS ? t2("almost done") : `~${remaining}s ${t2("left")}`;
19324
+ const statuses = [
19325
+ t2("Preparing your image"),
19326
+ t2("Analyzing body proportions"),
19327
+ t2("Mapping garment to body"),
19328
+ t2("Refining drape and shadows"),
19329
+ t2("Almost done")
19330
+ ];
19331
+ const stepIdx = Math.min(
19332
+ statuses.length - 1,
19333
+ Math.floor(elapsed / TARGET_SECONDS * statuses.length)
19334
+ );
19335
+ const status = statuses[stepIdx];
19336
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-tryon-badge", role: "status", "aria-live": "polite", children: [
19337
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-tryon-badge-row", children: [
19338
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "ps-tryon-badge-spinner", "aria-hidden": "true", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("svg", { viewBox: "0 0 24 24", width: "22", height: "22", children: [
19339
+ /* @__PURE__ */ jsxRuntimeExports.jsx("defs", { children: /* @__PURE__ */ jsxRuntimeExports.jsxs("linearGradient", { id: "ps-tryon-badge-grad", x1: "0", y1: "0", x2: "1", y2: "1", children: [
19340
+ /* @__PURE__ */ jsxRuntimeExports.jsx("stop", { offset: "0%", stopColor: "#3B82F6" }),
19341
+ /* @__PURE__ */ jsxRuntimeExports.jsx("stop", { offset: "100%", stopColor: "#2563EB" })
19342
+ ] }) }),
19343
+ /* @__PURE__ */ jsxRuntimeExports.jsx("circle", { cx: "12", cy: "12", r: "9", fill: "none", stroke: "rgba(59,130,246,0.18)", strokeWidth: "2.4" }),
19344
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
19345
+ "circle",
19346
+ {
19347
+ cx: "12",
19348
+ cy: "12",
19349
+ r: "9",
19350
+ fill: "none",
19351
+ stroke: "url(#ps-tryon-badge-grad)",
19352
+ strokeWidth: "2.4",
19353
+ strokeLinecap: "round",
19354
+ strokeDasharray: "56.5",
19355
+ strokeDashoffset: "38"
19356
+ }
19357
+ )
19358
+ ] }) }),
19359
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "ps-tryon-badge-title", children: t2("Generating your look...") }),
19360
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "ps-tryon-badge-pct", children: [
19361
+ pctRounded,
19362
+ "%"
19363
+ ] })
19364
+ ] }),
19365
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-tryon-badge-bar", "aria-hidden": "true", children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-tryon-badge-bar-fill", style: { width: `${pctRounded}%` } }) }),
19366
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-tryon-badge-foot", children: [
19367
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "ps-tryon-badge-status", children: [
19368
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "ps-tryon-badge-status-icon", "aria-hidden": "true", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("svg", { width: "12", height: "12", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
19369
+ /* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: "6", y1: "10", x2: "6", y2: "14" }),
19370
+ /* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: "10", y1: "6", x2: "10", y2: "18" }),
19371
+ /* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: "14", y1: "9", x2: "14", y2: "15" }),
19372
+ /* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: "18", y1: "11", x2: "18", y2: "13" })
19373
+ ] }) }),
19374
+ status
19375
+ ] }),
19376
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "ps-tryon-badge-eta", children: [
19377
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
19378
+ "svg",
19379
+ {
19380
+ width: "11",
19381
+ height: "11",
19382
+ viewBox: "0 0 24 24",
19383
+ fill: "none",
19384
+ stroke: "currentColor",
19385
+ strokeWidth: "2",
19386
+ strokeLinecap: "round",
19387
+ strokeLinejoin: "round",
19388
+ "aria-hidden": "true",
19389
+ children: [
19390
+ /* @__PURE__ */ jsxRuntimeExports.jsx("circle", { cx: "12", cy: "12", r: "10" }),
19391
+ /* @__PURE__ */ jsxRuntimeExports.jsx("polyline", { points: "12 6 12 12 16 14" })
19392
+ ]
19393
+ }
19394
+ ),
19395
+ etaText
19396
+ ] })
19397
+ ] })
19398
+ ] });
19399
+ }
19400
+ const PER_SLIDE = 3;
19401
+ const CYCLE_MS = 4e3;
19402
+ function ProductPhotoCarouselCard({
19403
+ photos,
19404
+ productTitle,
19405
+ t: t2
19406
+ }) {
19407
+ const [groupIdx, setGroupIdx] = reactExports.useState(0);
19408
+ const totalGroups = Math.max(1, Math.ceil(photos.length / PER_SLIDE));
19409
+ reactExports.useEffect(() => {
19410
+ if (totalGroups < 2) return;
19411
+ const id2 = setInterval(() => setGroupIdx((v2) => (v2 + 1) % totalGroups), CYCLE_MS);
19412
+ return () => clearInterval(id2);
19413
+ }, [totalGroups]);
19414
+ if (!photos || photos.length === 0) return null;
19415
+ const start = groupIdx * PER_SLIDE;
19416
+ const slide = photos.slice(start, start + PER_SLIDE);
19417
+ while (slide.length < PER_SLIDE && photos.length >= PER_SLIDE) {
19418
+ slide.push(photos[(start + slide.length) % photos.length]);
19419
+ }
19420
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-tryon-photo-strip", role: "group", "aria-label": t2("Product photos"), children: [
19421
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-tryon-photo-strip-head", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "ps-tryon-photo-strip-badge", children: [
19422
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("svg", { width: "11", height: "11", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.4", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: [
19423
+ /* @__PURE__ */ jsxRuntimeExports.jsx("rect", { x: "3", y: "3", width: "18", height: "18", rx: "2" }),
19424
+ /* @__PURE__ */ jsxRuntimeExports.jsx("circle", { cx: "9", cy: "9", r: "2" }),
19425
+ /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21" })
19426
+ ] }),
19427
+ t2("Gallery")
19428
+ ] }) }),
19429
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-tryon-photo-strip-row", children: slide.map((src, i) => /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-tryon-photo-strip-cell", children: /* @__PURE__ */ jsxRuntimeExports.jsx("img", { src, alt: productTitle || "", draggable: false }) }, `${groupIdx}-${i}`)) }, groupIdx),
19430
+ totalGroups > 1 && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-tryon-photo-strip-dots", "aria-hidden": "true", children: Array.from({ length: totalGroups }).map((_, i) => /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `ps-tryon-photo-strip-dot${i === groupIdx ? " is-active" : ""}` }, i)) })
19431
+ ] });
19432
+ }
19238
19433
  function garmentIconForSection(name) {
19239
19434
  const n2 = name.toLowerCase();
19240
19435
  if (n2.includes("jacket") || n2.includes("blazer") || n2.includes("coat")) return garmentJacketImg;
@@ -19258,62 +19453,6 @@ const SKELETON_CONNECTIONS = [
19258
19453
  ["rightHip", "rightKnee"],
19259
19454
  ["rightKnee", "rightAnkle"]
19260
19455
  ];
19261
- const TRYON_TARGET_SECONDS = 22;
19262
- const TRYON_RING_RADIUS = 27;
19263
- const TRYON_RING_CIRC = 2 * Math.PI * TRYON_RING_RADIUS;
19264
- function TryOnProgress({ t: t2, isActive }) {
19265
- const startRef = reactExports.useRef(null);
19266
- const ringRef = reactExports.useRef(null);
19267
- const barRef = reactExports.useRef(null);
19268
- const etaRef = reactExports.useRef(null);
19269
- const pctRef = reactExports.useRef(null);
19270
- reactExports.useEffect(() => {
19271
- if (!isActive) {
19272
- startRef.current = null;
19273
- return;
19274
- }
19275
- startRef.current = Date.now();
19276
- const id2 = setInterval(() => {
19277
- const start = startRef.current || Date.now();
19278
- const elapsed = (Date.now() - start) / 1e3;
19279
- const pct = Math.min(95, elapsed / TRYON_TARGET_SECONDS * 100);
19280
- const val = Math.round(pct);
19281
- if (barRef.current) barRef.current.style.width = `${val}%`;
19282
- if (pctRef.current) pctRef.current.textContent = `${val}%`;
19283
- if (ringRef.current) {
19284
- ringRef.current.style.strokeDashoffset = String(TRYON_RING_CIRC * (1 - pct / 100));
19285
- }
19286
- if (etaRef.current) {
19287
- const remaining = Math.max(0, TRYON_TARGET_SECONDS - Math.floor(elapsed));
19288
- etaRef.current.textContent = elapsed >= TRYON_TARGET_SECONDS ? "•••" : `~${remaining}s`;
19289
- }
19290
- }, 200);
19291
- return () => clearInterval(id2);
19292
- }, [isActive]);
19293
- if (!isActive) return null;
19294
- return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-tryon-progress-wrap", children: [
19295
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-tryon-progress-ring", children: [
19296
- /* @__PURE__ */ jsxRuntimeExports.jsxs("svg", { width: "64", height: "64", viewBox: "0 0 64 64", "aria-hidden": "true", children: [
19297
- /* @__PURE__ */ jsxRuntimeExports.jsx("circle", { cx: "32", cy: "32", r: TRYON_RING_RADIUS, className: "ps-tryon-progress-ring-track" }),
19298
- /* @__PURE__ */ jsxRuntimeExports.jsx(
19299
- "circle",
19300
- {
19301
- ref: ringRef,
19302
- cx: "32",
19303
- cy: "32",
19304
- r: TRYON_RING_RADIUS,
19305
- className: "ps-tryon-progress-ring-fill",
19306
- strokeDasharray: TRYON_RING_CIRC,
19307
- strokeDashoffset: TRYON_RING_CIRC
19308
- }
19309
- )
19310
- ] }),
19311
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { ref: etaRef, className: "ps-tryon-progress-eta", children: `~${TRYON_TARGET_SECONDS}s` })
19312
- ] }),
19313
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-tryon-progress-bar-wrap", children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { ref: barRef, className: "ps-tryon-progress-bar-fill" }) }),
19314
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { ref: pctRef, className: "ps-tryon-progress-pct", children: "0%" })
19315
- ] });
19316
- }
19317
19456
  function FaceOverlay({
19318
19457
  landmarks,
19319
19458
  imgWidth,
@@ -19440,14 +19579,20 @@ function StageCycler({
19440
19579
  { title: t2("MATCHING SIZE"), desc: t2("Comparing your measurements to the size guide.") },
19441
19580
  { title: t2("FINALIZING RESULT"), desc: t2("Almost done — preparing your recommendation.") }
19442
19581
  ];
19582
+ const TOTAL_MS = 6e3;
19583
+ const LAST_HOLD_MS = 1e3;
19584
+ const startRef = reactExports.useRef(Date.now());
19443
19585
  const [idx, setIdx] = reactExports.useState(0);
19444
19586
  reactExports.useEffect(() => {
19445
- if (sizingDone) return;
19587
+ const stepMs = (TOTAL_MS - LAST_HOLD_MS) / Math.max(1, sizingStages.length - 1);
19446
19588
  const id2 = setInterval(() => {
19447
- setIdx((i) => Math.min(i + 1, sizingStages.length - 1));
19448
- }, 900);
19589
+ const elapsed = Date.now() - startRef.current;
19590
+ const next = Math.min(sizingStages.length - 1, Math.floor(elapsed / stepMs));
19591
+ setIdx((prev) => prev === next ? prev : next);
19592
+ if (next >= sizingStages.length - 1) clearInterval(id2);
19593
+ }, 100);
19449
19594
  return () => clearInterval(id2);
19450
- }, [sizingDone, sizingStages.length]);
19595
+ }, [sizingStages.length]);
19451
19596
  const current = sizingStages[idx] ?? sizingStages[0];
19452
19597
  return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-msc-stage", style: { alignSelf: "center", marginTop: "auto", marginBottom: "auto" }, children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-msc-stage-slot", children: [
19453
19598
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-msc-stage-title", children: current.title }),
@@ -19721,19 +19866,11 @@ function SectionDetailView({
19721
19866
  internationalSizes,
19722
19867
  continueLabel,
19723
19868
  renderRaw = false,
19724
- sectionFound,
19725
- onSelectForTryOn,
19726
- onRegenerateTryOn,
19727
- pendingOverride,
19728
- retryLoading,
19729
- retryStartedAt
19869
+ sectionFound
19730
19870
  }) {
19731
19871
  const recSize = sectionResult?.recommendedSize || "";
19732
- const [selectedSize, setSelectedSize] = reactExports.useState(
19733
- pendingOverride?.selectedSize && pendingOverride.selectedSize !== (sectionResult?.recommendedSize || "") ? pendingOverride.selectedSize : null
19734
- );
19872
+ const [selectedSize, setSelectedSize] = reactExports.useState(null);
19735
19873
  const tryOnElapsedS = useElapsedSeconds(tryOnStartedAt ?? null);
19736
- const retryElapsedS = useElapsedSeconds(retryStartedAt ?? null);
19737
19874
  const unitLblLower = unitLbl.toLowerCase();
19738
19875
  const displayUnitId = unitLblLower.includes("mm") ? "mm" : unitLblLower.includes("cm") ? "cm" : "in";
19739
19876
  const fromUnit = chartUnit || displayUnitId;
@@ -19746,9 +19883,7 @@ function SectionDetailView({
19746
19883
  const countryOptions = internationalSizes ? Object.keys(internationalSizes) : [];
19747
19884
  const [selectedCountry, setSelectedCountry] = reactExports.useState(null);
19748
19885
  const recLength = lengthEntry?.secResult?.recommendedSize || "";
19749
- const [selectedLength, setSelectedLength] = reactExports.useState(
19750
- pendingOverride?.selectedLength && pendingOverride.selectedLength !== recLength ? pendingOverride.selectedLength : null
19751
- );
19886
+ const [selectedLength, setSelectedLength] = reactExports.useState(null);
19752
19887
  const lengthSizes = reactExports.useMemo(() => {
19753
19888
  if (!lengthEntry) return [];
19754
19889
  const sec = lengthEntry.section;
@@ -19975,8 +20110,8 @@ function SectionDetailView({
19975
20110
  const userInColUnit = colIsCm && userIsInches ? +(userNum2 * 2.54).toFixed(1) : !colIsCm && !userIsInches ? +(userNum2 * 2.54).toFixed(1) : userNum2;
19976
20111
  const range2 = rMaxRaw - rMinRaw;
19977
20112
  const threshold2 = range2 > 0 ? range2 * 0.5 : rMinRaw * 0.05 || 3;
19978
- const tol2 = Math.max((rMaxRaw || rMinRaw) * 5e-3, 0.25);
19979
- if (userInColUnit >= rMinRaw - tol2 && userInColUnit <= rMaxRaw + tol2) fit2 = "good";
20113
+ const tol = colIsCm ? 2.54 : 1;
20114
+ if (userInColUnit > rMinRaw - tol && userInColUnit < rMaxRaw + tol) fit2 = "good";
19980
20115
  else if (userInColUnit < rMinRaw) {
19981
20116
  const diff = rMinRaw - userInColUnit;
19982
20117
  fit2 = diff > threshold2 * 2 ? "too-long" : diff > threshold2 ? "long" : "a-bit-long";
@@ -20009,8 +20144,10 @@ function SectionDetailView({
20009
20144
  const measLower = m2.measurement.toLowerCase();
20010
20145
  const isDirectional = /length|inseam|sleeve|hem|rise/.test(measLower);
20011
20146
  let fit;
20012
- const tol = Math.max((rMax || rMin) * 0.03, 0.5);
20013
- if (userNum >= rMin - tol && userNum <= rMax + tol) {
20147
+ const perfectTol = chartUnit === "cm" ? 2.54 : chartUnit === "mm" ? 25.4 : 1;
20148
+ const lowBound = rMin - perfectTol;
20149
+ const highBound = rMax + perfectTol;
20150
+ if (userNum > lowBound && userNum < highBound) {
20014
20151
  fit = "good";
20015
20152
  } else if (isDirectional) {
20016
20153
  const diff = userNum > rMax ? userNum - rMax : rMin - userNum;
@@ -20057,65 +20194,6 @@ function SectionDetailView({
20057
20194
  const start = Math.max(0, Math.min(lengthOptions.length - 3, idx - 1));
20058
20195
  return lengthOptions.slice(start, start + 3);
20059
20196
  })();
20060
- const autoCommitInitialMount = reactExports.useRef(true);
20061
- reactExports.useLayoutEffect(() => {
20062
- if (autoCommitInitialMount.current) {
20063
- autoCommitInitialMount.current = false;
20064
- return;
20065
- }
20066
- if (!onSelectForTryOn) return;
20067
- const effSize = displaySize;
20068
- const effLength = selectedLength || backendLength || "";
20069
- const hasSizePick = !!selectedSize && selectedSize !== recSize;
20070
- const hasLengthPick = !!selectedLength && selectedLength !== backendLength;
20071
- if (!hasSizePick && !hasLengthPick) {
20072
- onSelectForTryOn(sectionName, null);
20073
- return;
20074
- }
20075
- const mainDetails = sectionResult?.matchDetails || [];
20076
- const lengthMeasurements = new Set(
20077
- (lengthEntry?.secResult?.matchDetails || []).map((m2) => m2.measurement.toLowerCase())
20078
- );
20079
- const overrideMd = mainDetails.length ? mainDetails.map((m2) => {
20080
- const measLc = m2.measurement.toLowerCase();
20081
- if (lengthMeasurements.has(measLc) && lengthEntry) {
20082
- const sec = lengthEntry.section;
20083
- const sizeCol = sec.headers.findIndex((h) => /size|length/i.test(h.trim()));
20084
- const sIdx = sizeCol >= 0 ? sizeCol : 0;
20085
- const targetCol = sec.headers.findIndex((h) => {
20086
- const hLc = h.toLowerCase().replace(/\s*\(.*?\)\s*/g, "").trim();
20087
- return !!hLc && (hLc === measLc || hLc.includes(measLc) || measLc.includes(hLc));
20088
- });
20089
- if (targetCol < 0) return m2;
20090
- const row = sec.rows.find((r2) => cellValFn(r2, sIdx, sec.headers[sIdx]) === effLength);
20091
- if (!row) return m2;
20092
- const cell = cellValFn(row, targetCol, sec.headers[targetCol]);
20093
- return cell ? { ...m2, chartRange: cell } : m2;
20094
- }
20095
- const alt = chartRangeFor(m2.measurement, effSize);
20096
- if (alt?.range) return { ...m2, chartRange: alt.range };
20097
- return m2;
20098
- }) : void 0;
20099
- const label = effLength ? `${effSize} / ${effLength}` : effSize;
20100
- console.log(`[ps-sdk:auto-commit] section="${sectionName}" label="${label}"`, {
20101
- selectedSize,
20102
- selectedLength,
20103
- recSize,
20104
- backendLength,
20105
- overrideMdPreview: overrideMd?.map((m2) => ({
20106
- area: m2.measurement,
20107
- userValue: m2.userValue,
20108
- chartRange: m2.chartRange
20109
- }))
20110
- });
20111
- onSelectForTryOn(sectionName, {
20112
- sectionName,
20113
- selectedSize: effSize,
20114
- selectedLength: hasLengthPick ? effLength : void 0,
20115
- displayLabel: label,
20116
- matchDetails: overrideMd
20117
- });
20118
- }, [selectedSize, selectedLength]);
20119
20197
  if (isMobileProp) {
20120
20198
  const cleanSectionName = sectionName.replace(/\s*[—–-]\s*.*/g, "");
20121
20199
  const measurementDesc = (area) => {
@@ -20599,169 +20677,7 @@ function SectionDetailView({
20599
20677
  s
20600
20678
  );
20601
20679
  }) })
20602
- ] }),
20603
- (onRegenerateTryOn || onSelectForTryOn && (!isRecommended || selectedLength && selectedLength !== backendLength)) && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { style: { marginTop: "0.6vw", marginBottom: "0.2vw" }, children: (() => {
20604
- const effSize = displaySize;
20605
- const effLength = selectedLength || backendLength || "";
20606
- const hasLengthPick = !!(selectedLength && selectedLength !== backendLength);
20607
- const label = effLength ? `${effSize} / ${effLength}` : effSize;
20608
- const buildOverrideMatchDetails = () => {
20609
- const mainDetails = sectionResult?.matchDetails || [];
20610
- if (!mainDetails.length) return void 0;
20611
- const lengthMeasurements = new Set(
20612
- (lengthEntry?.secResult?.matchDetails || []).map((m2) => m2.measurement.toLowerCase())
20613
- );
20614
- const out = mainDetails.map((m2) => {
20615
- const measLc = m2.measurement.toLowerCase();
20616
- if (lengthMeasurements.has(measLc) && lengthEntry) {
20617
- const numericPick = effLength && !Number.isNaN(parseFloat(effLength)) ? effLength : null;
20618
- if (numericPick) {
20619
- return { ...m2, chartRange: numericPick };
20620
- }
20621
- const sec = lengthEntry.section;
20622
- const sizeCol = sec.headers.findIndex((h) => /size|length/i.test(h.trim()));
20623
- const sIdx = sizeCol >= 0 ? sizeCol : 0;
20624
- const targetCol = sec.headers.findIndex((h) => {
20625
- const hLc = h.toLowerCase().replace(/\s*\(.*?\)\s*/g, "").trim();
20626
- return !!hLc && (hLc === measLc || hLc.includes(measLc) || measLc.includes(hLc));
20627
- });
20628
- if (targetCol < 0) return m2;
20629
- const row = sec.rows.find((r2) => cellValFn(r2, sIdx, sec.headers[sIdx]) === effLength);
20630
- if (!row) return m2;
20631
- const cell = cellValFn(row, targetCol, sec.headers[targetCol]);
20632
- return cell ? { ...m2, chartRange: cell } : m2;
20633
- }
20634
- const alt = chartRangeFor(m2.measurement, effSize);
20635
- if (alt?.range) return { ...m2, chartRange: alt.range };
20636
- return m2;
20637
- });
20638
- if (effLength && Number.isNaN(parseFloat(effLength))) {
20639
- out.push({
20640
- measurement: "Length",
20641
- userValue: "",
20642
- chartRange: effLength,
20643
- fit: "good"
20644
- });
20645
- }
20646
- return out;
20647
- };
20648
- const isStored = pendingOverride?.selectedSize === effSize;
20649
- if (onRegenerateTryOn) {
20650
- const isRegenerating = !!retryLoading;
20651
- const TARGET_S = 22;
20652
- const countdownS = isRegenerating ? Math.max(0, TARGET_S - retryElapsedS) : TARGET_S;
20653
- return /* @__PURE__ */ jsxRuntimeExports.jsxs(
20654
- "button",
20655
- {
20656
- type: "button",
20657
- "data-ps-regen": "1",
20658
- disabled: isRegenerating,
20659
- onClick: () => {
20660
- const md2 = buildOverrideMatchDetails();
20661
- onRegenerateTryOn({
20662
- sectionName,
20663
- selectedSize: effSize,
20664
- selectedLength: hasLengthPick ? effLength : void 0,
20665
- displayLabel: label,
20666
- matchDetails: md2
20667
- });
20668
- },
20669
- style: {
20670
- width: "100%",
20671
- padding: "0.55vw 0.8vw",
20672
- borderRadius: "0.45vw",
20673
- fontSize: "0.7vw",
20674
- fontWeight: 700,
20675
- background: "var(--ps-accent)",
20676
- color: "#FFFFFF",
20677
- border: "1.5px solid var(--ps-accent)",
20678
- cursor: isRegenerating ? "progress" : "pointer",
20679
- fontFamily: "inherit",
20680
- display: "flex",
20681
- alignItems: "center",
20682
- justifyContent: "center",
20683
- gap: "0.4vw",
20684
- position: "relative",
20685
- overflow: "hidden"
20686
- },
20687
- children: [
20688
- isRegenerating && /* @__PURE__ */ jsxRuntimeExports.jsx(
20689
- "div",
20690
- {
20691
- className: "ps-tryon-regen-fill",
20692
- "aria-hidden": "true"
20693
- },
20694
- retryStartedAt ?? "regen"
20695
- ),
20696
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { style: { position: "relative", zIndex: 1 }, children: isRegenerating ? /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
20697
- t2("Generating new try-on…"),
20698
- " ",
20699
- countdownS,
20700
- "s"
20701
- ] }) : /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
20702
- t2("Try It On"),
20703
- " ",
20704
- /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { style: { fontSize: "0.65vw", opacity: 0.9 }, children: [
20705
- "(",
20706
- label,
20707
- ")"
20708
- ] })
20709
- ] }) })
20710
- ]
20711
- }
20712
- );
20713
- }
20714
- return /* @__PURE__ */ jsxRuntimeExports.jsx(
20715
- "button",
20716
- {
20717
- type: "button",
20718
- onClick: () => {
20719
- if (!onSelectForTryOn) return;
20720
- onSelectForTryOn(sectionName, isStored ? null : {
20721
- sectionName,
20722
- selectedSize: effSize,
20723
- selectedLength: hasLengthPick ? effLength : void 0,
20724
- displayLabel: label,
20725
- matchDetails: buildOverrideMatchDetails()
20726
- });
20727
- },
20728
- style: {
20729
- width: "100%",
20730
- padding: "0.55vw 0.8vw",
20731
- borderRadius: "0.45vw",
20732
- fontSize: "0.7vw",
20733
- fontWeight: 700,
20734
- background: isStored ? "var(--ps-accent)" : "transparent",
20735
- color: isStored ? "#FFFFFF" : "var(--ps-accent)",
20736
- border: `1.5px ${isStored ? "solid" : "dashed"} var(--ps-accent)`,
20737
- cursor: "pointer",
20738
- fontFamily: "inherit",
20739
- display: "flex",
20740
- alignItems: "center",
20741
- justifyContent: "center",
20742
- gap: "0.4vw",
20743
- transition: "background 0.15s, color 0.15s"
20744
- },
20745
- children: isStored ? /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
20746
- t2("Will use this on Try On"),
20747
- " ",
20748
- /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { style: { fontSize: "0.65vw", opacity: 0.9 }, children: [
20749
- "(",
20750
- label,
20751
- ") ✓"
20752
- ] })
20753
- ] }) : /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
20754
- t2("Use this for Try On"),
20755
- " ",
20756
- /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { style: { fontSize: "0.65vw", opacity: 0.75 }, children: [
20757
- "(",
20758
- label,
20759
- ")"
20760
- ] })
20761
- ] })
20762
- }
20763
- );
20764
- })() })
20680
+ ] })
20765
20681
  ] }) }),
20766
20682
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { style: { display: "flex", alignItems: "center", justifyContent: "space-between", paddingTop: "0.6vw", borderTop: "1px solid rgba(0,0,0,0.06)", flexShrink: 0 }, children: [
20767
20683
  /* @__PURE__ */ jsxRuntimeExports.jsxs("button", { className: "ps-bp-back-btn", onClick: onBack, type: "button", style: { fontSize: "0.7vw" }, children: [
@@ -20837,15 +20753,13 @@ function SizeResultView({
20837
20753
  sizeGuide,
20838
20754
  resultImageUrl,
20839
20755
  productImage,
20756
+ productImages,
20840
20757
  productTitle,
20841
20758
  productMaterial,
20842
20759
  productDescription,
20843
20760
  sizingUnit,
20844
20761
  setView,
20845
20762
  handleDownload,
20846
- onRetryWithFit,
20847
- retryLoading,
20848
- retryStartedAt,
20849
20763
  selectedFile,
20850
20764
  previewUrl,
20851
20765
  handleFileSelect,
@@ -20864,7 +20778,6 @@ function SizeResultView({
20864
20778
  userHeightCm,
20865
20779
  pendingCustomSizes: pendingCustomSizesProp,
20866
20780
  onPendingCustomSizeChange,
20867
- onRegenerateTryOn,
20868
20781
  t: t2
20869
20782
  }) {
20870
20783
  const resultUnitRaw = (sizingResult?.unit || sizingUnit || "").toString().toLowerCase();
@@ -20970,9 +20883,6 @@ function SizeResultView({
20970
20883
  const [poseReady, setPoseReady] = reactExports.useState(false);
20971
20884
  const [imgDims, setImgDims] = reactExports.useState({ w: 800, h: 1200 });
20972
20885
  const pendingCustomSizes = pendingCustomSizesProp ?? {};
20973
- const setPendingCustomSize = (sectionName, override) => {
20974
- onPendingCustomSizeChange?.(sectionName, override);
20975
- };
20976
20886
  const handleImgLoad = reactExports.useCallback((e) => {
20977
20887
  const el2 = e.currentTarget;
20978
20888
  if (el2.naturalWidth && el2.naturalHeight) {
@@ -21141,7 +21051,7 @@ function SizeResultView({
21141
21051
  const prettyLength = lengthRec.replace(/\s+/g, " ").trim();
21142
21052
  return `${baseSize} / ${prettyLength}`;
21143
21053
  }, [lengthEntries, allSectionEntries]);
21144
- const heightLineLabel = reactExports.useMemo(() => {
21054
+ reactExports.useMemo(() => {
21145
21055
  const cm = userHeightCm || 0;
21146
21056
  if (!cm) return "";
21147
21057
  if (unitLbl === "in") {
@@ -21153,8 +21063,8 @@ function SizeResultView({
21153
21063
  return `${Math.round(cm)} cm`;
21154
21064
  }, [userHeightCm, unitLbl]);
21155
21065
  const hasPhoto = !!previewUrl;
21156
- const isSnapProcessing = hasPhoto && (tryOnProcessing || sizingLoading && !sizingResult);
21157
- const isSizingOnly = !hasPhoto && sizingLoading && !sizingResult;
21066
+ const isSnapProcessing = hasPhoto && sizingLoading;
21067
+ const isSizingOnly = !hasPhoto && sizingLoading;
21158
21068
  const sizingDone = !!sizingResult;
21159
21069
  const tryOnDone = !!resultImageUrl && !tryOnProcessing;
21160
21070
  const allDone = hasPhoto ? sizingDone && tryOnDone : sizingDone;
@@ -21221,20 +21131,12 @@ function SizeResultView({
21221
21131
  onLoad: handleImgLoad
21222
21132
  }
21223
21133
  ),
21224
- tryOnProcessing ? /* @__PURE__ */ jsxRuntimeExports.jsx(RegenScanOverlay, { active: true }) : measurementType === "face" || measurementType === "head" ? faceLandmarks && /* @__PURE__ */ jsxRuntimeExports.jsx(FaceOverlay, { landmarks: faceLandmarks, imgWidth: imgDims.w, imgHeight: imgDims.h }) : bodyLandmarks && /* @__PURE__ */ jsxRuntimeExports.jsx(SkeletonOverlay, { landmarks: bodyLandmarks, imgWidth: imgDims.w, imgHeight: imgDims.h })
21134
+ measurementType === "face" || measurementType === "head" ? faceLandmarks && /* @__PURE__ */ jsxRuntimeExports.jsx(FaceOverlay, { landmarks: faceLandmarks, imgWidth: imgDims.w, imgHeight: imgDims.h }) : bodyLandmarks && /* @__PURE__ */ jsxRuntimeExports.jsx(SkeletonOverlay, { landmarks: bodyLandmarks, imgWidth: imgDims.w, imgHeight: imgDims.h })
21225
21135
  ] }),
21226
21136
  (() => {
21227
21137
  const isFaceCategory = measurementType === "face" || measurementType === "head";
21228
21138
  isFaceCategory ? measurementType === "head" ? t2("Detecting head") : t2("Detecting face") : t2("Detecting body pose");
21229
- return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-tryon-sr-right-col", style: { display: "flex", alignItems: tryOnProcessing ? "stretch" : "center", justifyContent: "center" }, children: tryOnProcessing ? /* @__PURE__ */ jsxRuntimeExports.jsx(
21230
- EngagingTryOnView,
21231
- {
21232
- productMaterial,
21233
- productDescription,
21234
- variant: "panel-only",
21235
- t: t2
21236
- }
21237
- ) : /* @__PURE__ */ jsxRuntimeExports.jsx(
21139
+ return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-tryon-sr-right-col", style: { display: "flex", alignItems: "center", justifyContent: "center" }, children: /* @__PURE__ */ jsxRuntimeExports.jsx(
21238
21140
  StageCycler,
21239
21141
  {
21240
21142
  category: isFaceCategory ? measurementType : "body",
@@ -21272,11 +21174,6 @@ function SizeResultView({
21272
21174
  })(),
21273
21175
  onBack: () => setActiveSection(null),
21274
21176
  internationalSizes: entry.secResult?.internationalSizes,
21275
- onSelectForTryOn: setPendingCustomSize,
21276
- onRegenerateTryOn: isAccessory ? void 0 : resultImageUrl && onRegenerateTryOn ? onRegenerateTryOn : void 0,
21277
- retryLoading,
21278
- retryStartedAt,
21279
- pendingOverride: pendingCustomSizes[entry.name] ?? null,
21280
21177
  productImage: resultImageUrl || productImage,
21281
21178
  productTitle,
21282
21179
  isMobile: true,
@@ -21314,10 +21211,7 @@ function SizeResultView({
21314
21211
  ] });
21315
21212
  }
21316
21213
  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-tryon-v2", children: [
21317
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-tryon-v2-bg", style: { position: "relative" }, children: [
21318
- /* @__PURE__ */ jsxRuntimeExports.jsx("img", { src: resultImageUrl || productImage, alt: productTitle, className: "ps-tryon-v2-bg-img" }),
21319
- /* @__PURE__ */ jsxRuntimeExports.jsx(RegenScanOverlay, { active: !!retryLoading && !!resultImageUrl })
21320
- ] }),
21214
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-tryon-v2-bg", style: { position: "relative" }, children: /* @__PURE__ */ jsxRuntimeExports.jsx("img", { src: resultImageUrl || productImage, alt: productTitle, className: "ps-tryon-v2-bg-img" }) }),
21321
21215
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-tryon-v2-panel", children: [
21322
21216
  mismatchNotice,
21323
21217
  /* @__PURE__ */ jsxRuntimeExports.jsx(
@@ -21339,11 +21233,6 @@ function SizeResultView({
21339
21233
  })(),
21340
21234
  onBack: () => setActiveSection(null),
21341
21235
  internationalSizes: entry.secResult?.internationalSizes,
21342
- onSelectForTryOn: setPendingCustomSize,
21343
- onRegenerateTryOn: isAccessory ? void 0 : resultImageUrl && onRegenerateTryOn ? onRegenerateTryOn : void 0,
21344
- retryLoading,
21345
- retryStartedAt,
21346
- pendingOverride: pendingCustomSizes[entry.name] ?? null,
21347
21236
  t: t2
21348
21237
  }
21349
21238
  )
@@ -21413,11 +21302,8 @@ function SizeResultView({
21413
21302
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-tryon-v2", children: [
21414
21303
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-tryon-v2-bg", style: { position: "relative" }, children: [
21415
21304
  /* @__PURE__ */ jsxRuntimeExports.jsx("img", { src: tryOnProcessing && previewUrl ? previewUrl : resultImageUrl || productImage, alt: productTitle, className: "ps-tryon-v2-bg-img", onLoad: handleImgLoad }),
21416
- tryOnProcessing && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-tryon-v2-processing-label", children: [
21417
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: t2("Generating try-on...") }),
21418
- /* @__PURE__ */ jsxRuntimeExports.jsx(TryOnProgress, { t: t2, isActive: true })
21419
- ] }),
21420
- /* @__PURE__ */ jsxRuntimeExports.jsx(RegenScanOverlay, { active: !!retryLoading && !!resultImageUrl && !tryOnProcessing || !!tryOnProcessing }),
21305
+ tryOnProcessing && /* @__PURE__ */ jsxRuntimeExports.jsx(TryOnGenerationBadge, { tryOnStartedAt: tryOnStartedAt ?? null, t: t2 }),
21306
+ /* @__PURE__ */ jsxRuntimeExports.jsx(RegenScanOverlay, { active: !!tryOnProcessing }),
21421
21307
  resultImageUrl && !tryOnProcessing && poseReady && poseLines && /* @__PURE__ */ jsxRuntimeExports.jsx(MeasurementOverlay, { lines: poseLines, fitRows: (() => {
21422
21308
  const all = [...sizingResult?.matchDetails || []];
21423
21309
  if (sizingResult?.sections) {
@@ -21483,11 +21369,15 @@ function SizeResultView({
21483
21369
  /* @__PURE__ */ jsxRuntimeExports.jsx(
21484
21370
  "span",
21485
21371
  {
21486
- className: "ps-tryon-sr-card-v2-rec",
21487
- style: isOverridden ? { color: "#b45309", fontWeight: 600 } : void 0,
21488
- children: isOverridden ? t2("your selection · not recommended") : heightLineLabel ? `${t2("recommended")} · ${heightLineLabel}` : t2("recommended")
21372
+ className: `ps-tryon-sr-card-v2-rec-pill${isOverridden ? " is-overridden" : ""}`,
21373
+ children: isOverridden ? t2("YOUR SELECTION") : t2("RECOMMENDED")
21489
21374
  }
21490
- )
21375
+ ),
21376
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "ps-tryon-sr-card-v2-view", children: [
21377
+ t2("VIEW DETAILS"),
21378
+ " ",
21379
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { "aria-hidden": "true", children: "›" })
21380
+ ] })
21491
21381
  ] }),
21492
21382
  sectionImg && /* @__PURE__ */ jsxRuntimeExports.jsx("img", { src: sectionImg, alt: name, className: "ps-tryon-sr-card-v2-img" })
21493
21383
  ] }, name);
@@ -21578,11 +21468,6 @@ function SizeResultView({
21578
21468
  },
21579
21469
  backLabel: t2("Back"),
21580
21470
  internationalSizes: sizingResult?.internationalSizes,
21581
- onSelectForTryOn: setPendingCustomSize,
21582
- onRegenerateTryOn: isAccessory ? void 0 : resultImageUrl && onRegenerateTryOn ? onRegenerateTryOn : void 0,
21583
- retryLoading,
21584
- retryStartedAt,
21585
- pendingOverride: pendingCustomSizes[sectionName] ?? null,
21586
21471
  onTryOn: resultImageUrl || vtoExcluded ? void 0 : handleSingleTryOn,
21587
21472
  continueLabel: resultImageUrl ? t2("Continue Shopping") : void 0,
21588
21473
  tryOnProcessing,
@@ -21612,50 +21497,158 @@ function SizeResultView({
21612
21497
  }
21613
21498
  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-tryon-v2", children: [
21614
21499
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-tryon-v2-bg", style: { position: "relative" }, children: [
21615
- /* @__PURE__ */ jsxRuntimeExports.jsx("img", { src: resultImageUrl || productImage, alt: productTitle, className: "ps-tryon-v2-bg-img", onLoad: handleImgLoad }),
21616
- resultImageUrl && poseReady && poseLines && /* @__PURE__ */ jsxRuntimeExports.jsx(MeasurementOverlay, { lines: poseLines, fitRows: (sizingResult?.matchDetails || []).map((m2) => ({ area: m2.measurement, userNum: parseFloat(m2.userValue) || 0, chartLabel: m2.chartRange || "", fit: m2.fit })), show: showLines, imgWidth: imgDims.w, imgHeight: imgDims.h }),
21500
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
21501
+ "img",
21502
+ {
21503
+ src: tryOnProcessing && previewUrl ? previewUrl : resultImageUrl || productImage,
21504
+ alt: productTitle,
21505
+ className: "ps-tryon-v2-bg-img",
21506
+ onLoad: handleImgLoad
21507
+ }
21508
+ ),
21509
+ tryOnProcessing && /* @__PURE__ */ jsxRuntimeExports.jsx(TryOnGenerationBadge, { tryOnStartedAt: tryOnStartedAt ?? null, t: t2 }),
21510
+ /* @__PURE__ */ jsxRuntimeExports.jsx(RegenScanOverlay, { active: !!tryOnProcessing }),
21511
+ resultImageUrl && !tryOnProcessing && poseReady && poseLines && /* @__PURE__ */ jsxRuntimeExports.jsx(MeasurementOverlay, { lines: poseLines, fitRows: (sizingResult?.matchDetails || []).map((m2) => ({ area: m2.measurement, userNum: parseFloat(m2.userValue) || 0, chartLabel: m2.chartRange || "", fit: m2.fit })), show: showLines, imgWidth: imgDims.w, imgHeight: imgDims.h }),
21617
21512
  resultImageUrl && !tryOnProcessing && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { style: { position: "absolute", bottom: "0.5vw", left: "0.5vw", zIndex: 3, display: "flex", flexDirection: "column", gap: "0.3vw" }, children: [
21618
21513
  !isAccessory && /* @__PURE__ */ jsxRuntimeExports.jsx("button", { className: "ps-tryon-sr-glass-btn", onClick: () => setShowLines(!showLines), children: showLines ? t2("Hide Fit") : t2("Show Fit") }),
21619
21514
  /* @__PURE__ */ jsxRuntimeExports.jsx("button", { className: "ps-tryon-sr-glass-btn", onClick: handleDownload, children: t2("Download") })
21620
- ] }),
21621
- /* @__PURE__ */ jsxRuntimeExports.jsx(RegenScanOverlay, { active: !!retryLoading && !!resultImageUrl && !tryOnProcessing })
21515
+ ] })
21622
21516
  ] }),
21623
21517
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-tryon-v2-panel", children: [
21624
21518
  mismatchNotice,
21625
- /* @__PURE__ */ jsxRuntimeExports.jsx(
21626
- SectionDetailView,
21627
- {
21628
- sectionName,
21629
- section: singleSection,
21630
- sectionResult: singleResult,
21631
- sectionFound: sizingResult?.found,
21632
- userMeasurements: singleUserMeasurements,
21633
- unitLbl,
21634
- chartUnit: resultUnit,
21635
- lengthEntry: null,
21636
- onBack: () => {
21637
- if (resultImageUrl) {
21638
- onResetTryOn?.();
21639
- } else {
21640
- setView("body-profile");
21519
+ activeSection === sectionName ? (
21520
+ /* DETAIL VIEW — full size table, same as multi-garment */
21521
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
21522
+ SectionDetailView,
21523
+ {
21524
+ sectionName,
21525
+ section: singleSection,
21526
+ sectionResult: singleResult,
21527
+ sectionFound: sizingResult?.found,
21528
+ userMeasurements: singleUserMeasurements,
21529
+ unitLbl,
21530
+ chartUnit: resultUnit,
21531
+ lengthEntry: null,
21532
+ onBack: () => setActiveSection(null),
21533
+ backLabel: t2("Back"),
21534
+ internationalSizes: sizingResult?.internationalSizes,
21535
+ onTryOn: resultImageUrl || vtoExcluded ? void 0 : handleSingleTryOn,
21536
+ continueLabel: resultImageUrl ? t2("Continue Shopping") : void 0,
21537
+ tryOnProcessing,
21538
+ tryOnStartedAt,
21539
+ t: t2,
21540
+ renderRaw: isAccessory
21541
+ }
21542
+ )
21543
+ ) : sizingResult?.found === false ? (
21544
+ /* NO-FIT EMPTY STATE — match% < 50% or backend reports
21545
+ no valid size. Skip the card + gallery + try-on
21546
+ entirely so the user gets immediate feedback that
21547
+ this product doesn't carry their size. */
21548
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-tryon-nofit", children: [
21549
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-tryon-nofit-icon", "aria-hidden": "true", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("svg", { width: "36", height: "36", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.8", strokeLinecap: "round", strokeLinejoin: "round", children: [
21550
+ /* @__PURE__ */ jsxRuntimeExports.jsx("circle", { cx: "12", cy: "12", r: "10" }),
21551
+ /* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: "15", y1: "9", x2: "9", y2: "15" }),
21552
+ /* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: "9", y1: "9", x2: "15", y2: "15" })
21553
+ ] }) }),
21554
+ /* @__PURE__ */ jsxRuntimeExports.jsx("h3", { className: "ps-tryon-nofit-title", children: t2("No size matches your measurements") }),
21555
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "ps-tryon-nofit-sub", children: t2("This product's size chart doesn't carry a fit close enough to your body. Try another product or update your profile.") }),
21556
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-tryon-nofit-actions", children: [
21557
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
21558
+ "button",
21559
+ {
21560
+ className: "ps-bp-back-btn",
21561
+ onClick: () => {
21562
+ if (resultImageUrl) onResetTryOn?.();
21563
+ else setView("body-profile");
21564
+ },
21565
+ type: "button",
21566
+ children: [
21567
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "ps-bp-back-arrow", children: "←" }),
21568
+ " ",
21569
+ t2("Back")
21570
+ ]
21571
+ }
21572
+ ),
21573
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("button", { className: "ps-tryon-v2-cta", style: { marginTop: 0 }, onClick: onClose, type: "button", children: [
21574
+ t2("Continue Shopping"),
21575
+ " →"
21576
+ ] })
21577
+ ] })
21578
+ ] })
21579
+ ) : (
21580
+ /* CARD VIEW — clickable summary card + gallery strip */
21581
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
21582
+ /* @__PURE__ */ jsxRuntimeExports.jsx("h3", { className: "ps-tryon-v2-title", children: t2("Your Perfect Fit") }),
21583
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "ps-tryon-v2-subtitle", children: t2("Tap the card for detailed breakdown") }),
21584
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-tryon-v2-sep" }),
21585
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-tryon-sr-cards-v2", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
21586
+ "button",
21587
+ {
21588
+ className: `ps-tryon-sr-card-v2 ps-full${pendingCustomSizes[sectionName] ? " ps-overridden" : ""}`,
21589
+ onClick: () => setActiveSection(sectionName),
21590
+ type: "button",
21591
+ children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-tryon-sr-card-v2-text", children: [
21592
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "ps-tryon-sr-card-v2-label", children: sectionName }),
21593
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "ps-tryon-sr-card-v2-value", children: pendingCustomSizes[sectionName]?.displayLabel || singleResult.recommendedSize || "—" }),
21594
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
21595
+ "span",
21596
+ {
21597
+ className: `ps-tryon-sr-card-v2-rec-pill${pendingCustomSizes[sectionName] ? " is-overridden" : ""}`,
21598
+ children: pendingCustomSizes[sectionName] ? t2("YOUR SELECTION") : t2("RECOMMENDED")
21599
+ }
21600
+ ),
21601
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "ps-tryon-sr-card-v2-view", children: [
21602
+ t2("VIEW DETAILS"),
21603
+ " ",
21604
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { "aria-hidden": "true", children: "›" })
21605
+ ] })
21606
+ ] })
21641
21607
  }
21642
- },
21643
- backLabel: t2("Back"),
21644
- internationalSizes: sizingResult?.internationalSizes,
21645
- onSelectForTryOn: setPendingCustomSize,
21646
- onRegenerateTryOn: isAccessory ? void 0 : resultImageUrl && onRegenerateTryOn ? onRegenerateTryOn : void 0,
21647
- retryLoading,
21648
- retryStartedAt,
21649
- pendingOverride: pendingCustomSizes[sectionName] ?? null,
21650
- onTryOn: resultImageUrl || vtoExcluded ? void 0 : handleSingleTryOn,
21651
- continueLabel: resultImageUrl ? t2("Continue Shopping") : void 0,
21652
- tryOnProcessing,
21653
- tryOnStartedAt,
21654
- t: t2,
21655
- renderRaw: isAccessory
21656
- }
21608
+ ) }),
21609
+ productImages && productImages.length >= 2 && /* @__PURE__ */ jsxRuntimeExports.jsx(ProductPhotoCarouselCard, { photos: productImages, productTitle, t: t2 }),
21610
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { style: { display: "flex", alignItems: "center", justifyContent: "space-between", marginTop: "0.5vw", gap: "0.5vw" }, children: [
21611
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
21612
+ "button",
21613
+ {
21614
+ className: "ps-bp-back-btn",
21615
+ onClick: () => {
21616
+ if (resultImageUrl) onResetTryOn?.();
21617
+ else setView("body-profile");
21618
+ },
21619
+ type: "button",
21620
+ children: [
21621
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "ps-bp-back-arrow", children: "←" }),
21622
+ " ",
21623
+ t2("Back")
21624
+ ]
21625
+ }
21626
+ ),
21627
+ resultImageUrl && !tryOnProcessing ? /* @__PURE__ */ jsxRuntimeExports.jsxs("button", { className: "ps-tryon-v2-cta", style: { marginTop: 0 }, onClick: onClose, children: [
21628
+ t2("Continue Shopping"),
21629
+ " →"
21630
+ ] }) : vtoExcluded ? /* @__PURE__ */ jsxRuntimeExports.jsxs("button", { className: "ps-tryon-v2-cta", style: { marginTop: 0 }, onClick: onClose, children: [
21631
+ t2("Continue Shopping"),
21632
+ " →"
21633
+ ] }) : /* @__PURE__ */ jsxRuntimeExports.jsxs(
21634
+ "button",
21635
+ {
21636
+ className: "ps-tryon-v2-cta",
21637
+ style: { marginTop: 0 },
21638
+ disabled: tryOnProcessing,
21639
+ onClick: handleSingleTryOn,
21640
+ type: "button",
21641
+ children: [
21642
+ /* @__PURE__ */ jsxRuntimeExports.jsx(CameraIcon$1, { size: 14 }),
21643
+ " ",
21644
+ tryOnProcessing ? t2("Processing...") : t2("Try It On")
21645
+ ]
21646
+ }
21647
+ )
21648
+ ] })
21649
+ ] })
21657
21650
  )
21658
- ] }, "panel-single")
21651
+ ] }, `panel-single-${activeSection ? "detail" : "card"}`)
21659
21652
  ] });
21660
21653
  })()
21661
21654
  ),
@@ -27696,6 +27689,19 @@ function detectMeasurementType(title) {
27696
27689
  if (/\b(sunglass|sunglasses|eyewear|eyeglasses|glasses|spectacles|optical|goggles|frames|aviator|wayfarer|lens)\b/.test(t2)) return "face";
27697
27690
  return "body";
27698
27691
  }
27692
+ function computeMatchScore(recData) {
27693
+ if (!recData) return null;
27694
+ const all = [];
27695
+ for (const m2 of recData.matchDetails ?? []) all.push(m2);
27696
+ if (recData.sections) {
27697
+ for (const sec of Object.values(recData.sections)) {
27698
+ for (const m2 of sec?.matchDetails ?? []) all.push(m2);
27699
+ }
27700
+ }
27701
+ if (all.length === 0) return null;
27702
+ const good = all.filter((r2) => r2.fit === "good" || r2.fit === "a-bit-tight" || r2.fit === "a-bit-loose").length;
27703
+ return Math.round(good / all.length * 100);
27704
+ }
27699
27705
  function measurementTypeToVtoCategory(type) {
27700
27706
  if (type === "face") return "sunglasses";
27701
27707
  if (type === "head") return "hat";
@@ -27704,6 +27710,8 @@ function measurementTypeToVtoCategory(type) {
27704
27710
  }
27705
27711
  function PrimeStyleTryonInner({
27706
27712
  productImage,
27713
+ productImages,
27714
+ garmentReferenceImage,
27707
27715
  productTitle = "Product",
27708
27716
  productId,
27709
27717
  productDescription,
@@ -27744,9 +27752,7 @@ function PrimeStyleTryonInner({
27744
27752
  const [resultImageUrl, setResultImageUrl] = reactExports.useState(null);
27745
27753
  const [errorMessage, setErrorMessage] = reactExports.useState(null);
27746
27754
  const [dragOver, setDragOver] = reactExports.useState(false);
27747
- const [retryLoading, setRetryLoading] = reactExports.useState(false);
27748
27755
  const [tryOnStartedAt, setTryOnStartedAt] = reactExports.useState(null);
27749
- const [retryStartedAt, setRetryStartedAt] = reactExports.useState(null);
27750
27756
  const [activeSection, setActiveSection] = reactExports.useState(null);
27751
27757
  const [pendingCustomSizes, setPendingCustomSizes] = reactExports.useState({});
27752
27758
  const pendingCustomSizesRef = reactExports.useRef({});
@@ -27756,7 +27762,7 @@ function PrimeStyleTryonInner({
27756
27762
  const sizingResultRef = reactExports.useRef(null);
27757
27763
  const sizeGuideRef = reactExports.useRef(null);
27758
27764
  const resultImageUrlRef = reactExports.useRef(null);
27759
- const setPendingCustomSizeBySection = reactExports.useCallback((sectionName, override) => {
27765
+ reactExports.useCallback((sectionName, override) => {
27760
27766
  setPendingCustomSizes((prev) => {
27761
27767
  const next = { ...prev };
27762
27768
  if (override === null) delete next[sectionName];
@@ -27778,6 +27784,7 @@ function PrimeStyleTryonInner({
27778
27784
  const [estimationDone, setEstimationDone] = reactExports.useState(false);
27779
27785
  const [tryOnProcessing, setTryOnProcessing] = reactExports.useState(false);
27780
27786
  const [sizeGuide, setSizeGuide] = reactExports.useState(null);
27787
+ const noFitFoundRef = reactExports.useRef(false);
27781
27788
  reactExports.useEffect(() => {
27782
27789
  sizingResultRef.current = sizingResult;
27783
27790
  }, [sizingResult]);
@@ -27836,6 +27843,7 @@ function PrimeStyleTryonInner({
27836
27843
  const [faceLandmarks, setFaceLandmarks] = reactExports.useState(null);
27837
27844
  const selectedFileRef = reactExports.useRef(null);
27838
27845
  const modelImageIdRef = reactExports.useRef(null);
27846
+ const autoTryOnFiredRef = reactExports.useRef(false);
27839
27847
  reactExports.useEffect(() => {
27840
27848
  try {
27841
27849
  const key = getApiKey();
@@ -28243,6 +28251,15 @@ function PrimeStyleTryonInner({
28243
28251
  unsubRef.current = null;
28244
28252
  }, []);
28245
28253
  const handleVtoUpdate = reactExports.useCallback((update) => {
28254
+ if (noFitFoundRef.current) {
28255
+ if (update.status === "completed" || update.status === "failed") {
28256
+ completedRef.current = true;
28257
+ cleanupJob();
28258
+ setTryOnProcessing(false);
28259
+ setTryOnStartedAt(null);
28260
+ }
28261
+ return;
28262
+ }
28246
28263
  if (update.status === "completed" && update.imageUrl) {
28247
28264
  setResultImageUrl((prev) => {
28248
28265
  if (!prev || prev.startsWith("data:")) return update.imageUrl;
@@ -28425,11 +28442,15 @@ function PrimeStyleTryonInner({
28425
28442
  }
28426
28443
  setEstimationDone(false);
28427
28444
  try {
28445
+ const tReq = Date.now();
28446
+ const payloadBytes = JSON.stringify(payload).length;
28447
+ console.log(`[ps-sdk:T] ▶ POST /sizing/recommend (quick) payload=${Math.round(payloadBytes / 1024)}KB bodyImage=${!!payload.bodyImage}`);
28428
28448
  const res = await fetch(`${baseUrl}/api/v1/sizing/recommend`, {
28429
28449
  method: "POST",
28430
28450
  headers: { "Content-Type": "application/json", Authorization: `Bearer ${key}` },
28431
28451
  body: JSON.stringify(payload)
28432
28452
  });
28453
+ console.log(`[ps-sdk:T] ◀ /sizing/recommend (quick) status=${res.status} roundTrip=${Date.now() - tReq}ms`);
28433
28454
  if (res.ok) {
28434
28455
  const data = await res.json();
28435
28456
  console.log("[PS-SDK] Sizing recommend RESULT:", JSON.stringify(data));
@@ -28565,6 +28586,8 @@ function PrimeStyleTryonInner({
28565
28586
  const objUrl = data.photoFile ? URL.createObjectURL(data.photoFile) : data.photoBase64.startsWith("data:") ? data.photoBase64 : `data:image/jpeg;base64,${data.photoBase64}`;
28566
28587
  setPreviewUrl(objUrl);
28567
28588
  completedRef.current = false;
28589
+ modelImageIdRef.current = null;
28590
+ noFitFoundRef.current = false;
28568
28591
  setTryOnProcessing(false);
28569
28592
  setTryOnStartedAt(null);
28570
28593
  setSizingResult(null);
@@ -28575,12 +28598,12 @@ function PrimeStyleTryonInner({
28575
28598
  const measurementType = detectMeasurementType(productTitle);
28576
28599
  if (measurementType === "face" || measurementType === "head") {
28577
28600
  setFaceLandmarks(null);
28578
- const minVisible = new Promise((r2) => setTimeout(r2, 4500));
28601
+ const minVisible2 = new Promise((r2) => setTimeout(r2, 4500));
28579
28602
  try {
28580
28603
  const faceResult = await detectFaceMeasurements(objUrl);
28581
28604
  if (!faceResult) {
28582
28605
  console.warn("[ps-sdk] face detection returned no result — likely a full-body photo for a face/head product");
28583
- await minVisible;
28606
+ await minVisible2;
28584
28607
  const msg = measurementType === "head" ? t2("We couldn't detect your head clearly. Please upload a close-up photo that shows your full head and ears.") : t2("We couldn't detect your face clearly. Please upload a close-up selfie that shows both eyes.");
28585
28608
  setErrorMessage(msg);
28586
28609
  setView("error");
@@ -28608,7 +28631,7 @@ function PrimeStyleTryonInner({
28608
28631
  });
28609
28632
  if (recRes.ok) {
28610
28633
  const recData = await recRes.json();
28611
- await minVisible;
28634
+ await minVisible2;
28612
28635
  setSizingResult(recData);
28613
28636
  onComplete?.(recData);
28614
28637
  persistResultToProfile(
@@ -28625,12 +28648,12 @@ function PrimeStyleTryonInner({
28625
28648
  { skipBodyEstimate: true }
28626
28649
  );
28627
28650
  } else {
28628
- await minVisible;
28651
+ await minVisible2;
28629
28652
  setEstimationDone(true);
28630
28653
  }
28631
28654
  } catch (err) {
28632
28655
  console.error("[ps-sdk] face-recommend failed:", err);
28633
- await minVisible;
28656
+ await minVisible2;
28634
28657
  setEstimationDone(true);
28635
28658
  }
28636
28659
  setSizingLoading(false);
@@ -28700,12 +28723,17 @@ function PrimeStyleTryonInner({
28700
28723
  const jointCount = lmObj ? Object.keys(lmObj).filter((k2) => k2 !== "imageWidth" && k2 !== "imageHeight" && lmObj[k2]).length : 0;
28701
28724
  console.log(`[ps-sdk:debug] payload → bodyLandmarks=${!!lmObj}(${jointCount} joints)`);
28702
28725
  }
28726
+ const minVisible = new Promise((r2) => setTimeout(r2, 6e3));
28703
28727
  try {
28728
+ const tReq = Date.now();
28729
+ const payloadBytes = JSON.stringify(payload).length;
28730
+ console.log(`[ps-sdk:T] ▶ POST /sizing/recommend payload=${Math.round(payloadBytes / 1024)}KB bodyImage=${!!payload.bodyImage} landmarks=${!!payload.bodyLandmarks}`);
28704
28731
  const recRes = await fetch(`${baseUrl}/api/v1/sizing/recommend`, {
28705
28732
  method: "POST",
28706
28733
  headers: { "Content-Type": "application/json", Authorization: `Bearer ${key}` },
28707
28734
  body: JSON.stringify(payload)
28708
28735
  });
28736
+ console.log(`[ps-sdk:T] ◀ /sizing/recommend status=${recRes.status} roundTrip=${Date.now() - tReq}ms`);
28709
28737
  if (recRes.ok) {
28710
28738
  const recData = await recRes.json();
28711
28739
  if (recData?.found === false && recData?.reasoning === "NO_SIZE_CHART") {
@@ -28714,26 +28742,38 @@ function PrimeStyleTryonInner({
28714
28742
  setSizingLoading(false);
28715
28743
  return;
28716
28744
  }
28717
- setSizingResult(recData);
28718
- onComplete?.(recData);
28719
- persistResultToProfile(
28720
- {
28721
- gender: data.gender,
28722
- height: data.height,
28723
- weight: data.weight,
28724
- heightUnit: data.heightUnit,
28725
- weightUnit: data.weightUnit,
28726
- age: data.age,
28727
- bodyImage: data.photoBase64
28728
- },
28729
- recData
28730
- );
28745
+ const matchScore = computeMatchScore(recData);
28746
+ const isLowFit = recData?.found === false || matchScore != null && matchScore < 50;
28747
+ if (isLowFit) {
28748
+ console.log(`[ps-sdk:gate] LOW FIT (match=${matchScore ?? "?"}%, found=${recData?.found}) — suppressing try-on`);
28749
+ noFitFoundRef.current = true;
28750
+ setTryOnProcessing(false);
28751
+ setTryOnStartedAt(null);
28752
+ setSizingResult({ ...recData, found: false });
28753
+ onComplete?.({ ...recData, found: false });
28754
+ } else {
28755
+ setSizingResult(recData);
28756
+ onComplete?.(recData);
28757
+ persistResultToProfile(
28758
+ {
28759
+ gender: data.gender,
28760
+ height: data.height,
28761
+ weight: data.weight,
28762
+ heightUnit: data.heightUnit,
28763
+ weightUnit: data.weightUnit,
28764
+ age: data.age,
28765
+ bodyImage: data.photoBase64
28766
+ },
28767
+ recData
28768
+ );
28769
+ }
28731
28770
  } else {
28732
28771
  setEstimationDone(true);
28733
28772
  }
28734
28773
  } catch {
28735
28774
  setEstimationDone(true);
28736
28775
  }
28776
+ await minVisible;
28737
28777
  setSizingLoading(false);
28738
28778
  }, [apiUrl, productImage, productTitle, sizingUnit, weightUnit, sizingCountry, sizeGuide, dynamicFields, persistResultToProfile]);
28739
28779
  snapSubmitRef.current = handleSnapSubmit;
@@ -28757,9 +28797,9 @@ function PrimeStyleTryonInner({
28757
28797
  const isApparel = vtoCategory === "apparel";
28758
28798
  const previewObjUrl = (overrideFile ? null : previewUrl) || URL.createObjectURL(file);
28759
28799
  if (overrideFile || !previewUrl) setPreviewUrl(previewObjUrl);
28760
- modelPoseRef.current = null;
28761
- setBodyLandmarks(null);
28762
- if (isApparel) {
28800
+ if (isApparel && (!modelPoseRef.current || !bodyLandmarks)) {
28801
+ modelPoseRef.current = null;
28802
+ setBodyLandmarks(null);
28763
28803
  detectMeasurementLines(previewObjUrl).then((lines) => {
28764
28804
  modelPoseRef.current = lines;
28765
28805
  }).catch(() => {
@@ -28810,20 +28850,20 @@ function PrimeStyleTryonInner({
28810
28850
  console.log("[ps-sdk:flatten] sizingResult keys:", Object.keys(sizingResult || {}));
28811
28851
  console.log("[ps-sdk:flatten] root matchDetails:", (sizingResult?.matchDetails || []).map((m2) => m2.measurement));
28812
28852
  console.log("[ps-sdk:flatten] sections:", sizingResult?.sections ? Object.keys(sizingResult.sections) : "none");
28813
- const push = (md2, src) => {
28853
+ const push = (md2, src, section) => {
28814
28854
  if (!md2) return;
28815
28855
  console.log(`[ps-sdk:flatten] ${src} →`, md2.map((m2) => m2.measurement));
28816
28856
  for (const m2 of md2) {
28817
28857
  const k2 = m2.measurement.toLowerCase().replace(/\s*\(.*?\)\s*/g, "").trim();
28818
28858
  if (seen.has(k2)) continue;
28819
28859
  seen.add(k2);
28820
- out.push(m2);
28860
+ out.push({ ...m2, section });
28821
28861
  }
28822
28862
  };
28823
- push(sizingResult?.matchDetails, "root");
28863
+ push(sizingResult?.matchDetails, "root", void 0);
28824
28864
  if (sizingResult?.sections) {
28825
28865
  for (const [secName, sec] of Object.entries(sizingResult.sections)) {
28826
- push(sec?.matchDetails, `section:${secName}`);
28866
+ push(sec?.matchDetails, `section:${secName}`, secName);
28827
28867
  }
28828
28868
  }
28829
28869
  console.log("[ps-sdk:flatten] final unique:", out.map((m2) => m2.measurement));
@@ -28832,12 +28872,24 @@ function PrimeStyleTryonInner({
28832
28872
  const effectiveMatchDetails = override?.matchDetails && override.matchDetails.length ? override.matchDetails : flattenAllMatchDetails();
28833
28873
  let fitInfo;
28834
28874
  if (isApparel && effectiveMatchDetails.length) {
28835
- fitInfo = buildFitInfo(effectiveMatchDetails, modelPoseRef.current);
28875
+ const unitRaw = (sizingResult?.unit || sizingUnit || "in").toString().toLowerCase();
28876
+ const unit = unitRaw === "cm" ? "cm" : unitRaw === "mm" ? "mm" : "in";
28877
+ fitInfo = buildFitInfo(effectiveMatchDetails, modelPoseRef.current, unit);
28836
28878
  }
28837
28879
  console.log("[ps-sdk:tryon] fitInfo built", { count: fitInfo?.length || 0, areas: fitInfo?.map((f2) => `${f2.area}(${f2.fit})`) });
28880
+ let garmentImage = productImage;
28881
+ if (garmentReferenceImage) {
28882
+ garmentImage = garmentReferenceImage;
28883
+ } else if (productImages && productImages.length > 1) {
28884
+ const best = await pickBestGarmentImage(productImages);
28885
+ if (best && best !== productImage) {
28886
+ console.log(`[ps-sdk:tryon] auto-picked garment reference: ${best}`);
28887
+ garmentImage = best;
28888
+ }
28889
+ }
28838
28890
  const response = await apiRef.current.submitTryOn(
28839
28891
  modelImage,
28840
- productImage,
28892
+ garmentImage,
28841
28893
  fitInfo,
28842
28894
  vtoCategory ?? "apparel",
28843
28895
  {
@@ -28881,167 +28933,19 @@ function PrimeStyleTryonInner({
28881
28933
  onError?.({ message, code });
28882
28934
  }
28883
28935
  }, [selectedFile, productImage, productTitle, sizingResult, onProcessing, onError, handleVtoUpdate]);
28884
- const handleRetryWithFit = reactExports.useCallback(async (fitInfo, selectedSizeOverride) => {
28885
- const file = selectedFile || selectedFileRef.current;
28886
- if (!file || !apiRef.current || !sseRef.current) {
28887
- console.warn("[ps-sdk:retry] skipping — no file/api/sse", {
28888
- hasFile: !!file,
28889
- hasApi: !!apiRef.current,
28890
- hasSse: !!sseRef.current
28891
- });
28892
- return;
28893
- }
28894
- console.log("[ps-sdk:retry] starting", { fitInfoCount: fitInfo.length, selectedSizeOverride, fitInfo });
28895
- setRetryLoading(true);
28896
- setRetryStartedAt(Date.now());
28897
- const vtoCategory = measurementTypeToVtoCategory(detectMeasurementType(productTitle));
28898
- const isApparel = vtoCategory === "apparel";
28899
- if (isApparel && modelPoseRef.current) {
28900
- const AREA_MAP = {
28901
- chest: "chest",
28902
- bust: "chest",
28903
- waist: "waist",
28904
- hips: "hips",
28905
- hip: "hips"
28906
- };
28907
- for (const area of fitInfo) {
28908
- const key = AREA_MAP[area.area.toLowerCase()];
28909
- if (key && modelPoseRef.current[key]) {
28910
- const line = modelPoseRef.current[key];
28911
- area.y = Math.round(line.y * 1e3) / 1e3;
28912
- area.x1 = Math.round(line.x1 * 1e3) / 1e3;
28913
- area.x2 = Math.round(line.x2 * 1e3) / 1e3;
28914
- }
28915
- }
28916
- }
28917
- try {
28918
- completedRef.current = false;
28919
- unsubRef.current?.();
28920
- if (pollingRef.current) {
28921
- clearInterval(pollingRef.current);
28922
- pollingRef.current = null;
28923
- }
28924
- const useExistingResult = false;
28925
- const modelImage = await compressImage(file, { maxDimension: 1024, quality: 0.85 });
28926
- console.log("[ps-sdk:retry] modelImage source", {
28927
- useExistingResult,
28928
- bytes: modelImage.length,
28929
- isPng: modelImage.startsWith("data:image/png")
28930
- });
28931
- const outboundFitInfo = isApparel ? fitInfo : void 0;
28932
- const response = await apiRef.current.submitTryOn(
28933
- modelImage,
28934
- productImage,
28935
- outboundFitInfo,
28936
- vtoCategory ?? "apparel",
28937
- {
28938
- editFromPrevious: useExistingResult,
28939
- productId: effectiveProductId,
28940
- productTitle,
28941
- productDescription,
28942
- productMaterial,
28943
- silhouetteContext: buildSilhouetteContext(sizingResultRef.current, sizeGuideRef.current, selectedSizeOverride),
28944
- modelImageId: modelImageIdRef.current ?? void 0
28945
- }
28946
- );
28947
- if (response.modelImageId) modelImageIdRef.current = response.modelImageId;
28948
- unsubRef.current = sseRef.current.onJob(response.jobId, (update) => {
28949
- if (update.status === "completed" && update.imageUrl) {
28950
- setResultImageUrl(update.imageUrl);
28951
- setRetryLoading(false);
28952
- setRetryStartedAt(null);
28953
- completedRef.current = true;
28954
- unsubRef.current?.();
28955
- unsubRef.current = null;
28956
- if (pollingRef.current) {
28957
- clearInterval(pollingRef.current);
28958
- pollingRef.current = null;
28959
- }
28960
- } else if (update.status === "failed") {
28961
- setRetryLoading(false);
28962
- setRetryStartedAt(null);
28963
- completedRef.current = true;
28964
- unsubRef.current?.();
28965
- unsubRef.current = null;
28966
- if (pollingRef.current) {
28967
- clearInterval(pollingRef.current);
28968
- pollingRef.current = null;
28969
- }
28970
- }
28971
- });
28972
- let attempts = 0;
28973
- pollingRef.current = setInterval(async () => {
28974
- if (completedRef.current) {
28975
- if (pollingRef.current) clearInterval(pollingRef.current);
28976
- pollingRef.current = null;
28977
- return;
28978
- }
28979
- attempts++;
28980
- if (attempts > 60) {
28981
- if (pollingRef.current) clearInterval(pollingRef.current);
28982
- pollingRef.current = null;
28983
- setRetryLoading(false);
28984
- setRetryStartedAt(null);
28985
- return;
28986
- }
28987
- try {
28988
- const status = await apiRef.current.getStatus(response.jobId);
28989
- if (status.status === "completed" && status.imageUrl) {
28990
- if (!completedRef.current) {
28991
- completedRef.current = true;
28992
- setResultImageUrl(status.imageUrl);
28993
- setRetryLoading(false);
28994
- setRetryStartedAt(null);
28995
- unsubRef.current?.();
28996
- unsubRef.current = null;
28997
- }
28998
- if (pollingRef.current) {
28999
- clearInterval(pollingRef.current);
29000
- pollingRef.current = null;
29001
- }
29002
- } else if (status.status === "failed") {
29003
- completedRef.current = true;
29004
- setRetryLoading(false);
29005
- setRetryStartedAt(null);
29006
- if (pollingRef.current) {
29007
- clearInterval(pollingRef.current);
29008
- pollingRef.current = null;
29009
- }
29010
- }
29011
- } catch {
29012
- }
29013
- }, 3e3);
29014
- } catch {
29015
- setRetryLoading(false);
29016
- setRetryStartedAt(null);
29017
- }
29018
- }, [selectedFile, productImage, productTitle]);
29019
- const handleRegenerateTryOn = reactExports.useCallback((override) => {
29020
- console.log("[ps-sdk:regen] fired", { sectionName: override.sectionName, override, hasFile: !!selectedFile, hasRefFile: !!selectedFileRef.current });
29021
- setPendingCustomSizeBySection(override.sectionName, override);
29022
- const map = { ...pendingCustomSizesRef.current, [override.sectionName]: override };
29023
- const seen = /* @__PURE__ */ new Set();
29024
- const merged = [];
29025
- for (const key of Object.keys(map)) {
29026
- for (const m2 of map[key].matchDetails || []) {
29027
- const k2 = m2.measurement.toLowerCase().trim();
29028
- if (seen.has(k2)) continue;
29029
- seen.add(k2);
29030
- merged.push(m2);
29031
- }
29032
- }
29033
- if (!merged.length) {
29034
- console.warn("[ps-sdk:regen] no matchDetails across any section — cannot rebuild fitInfo");
28936
+ reactExports.useEffect(() => {
28937
+ if (!sizingResult) {
28938
+ autoTryOnFiredRef.current = false;
29035
28939
  return;
29036
28940
  }
29037
- const fitInfo = buildFitInfo(merged, modelPoseRef.current);
29038
- const compositeLabel = Object.keys(map).map((s) => {
29039
- const cleanName = s.replace(/\s*[—–-]\s*.*/g, "").trim();
29040
- return `${cleanName} ${map[s].displayLabel}`;
29041
- }).join(", ");
29042
- console.log("[ps-sdk:regen] merged fitInfo built", { fitInfo, compositeLabel, sections: Object.keys(map) });
29043
- handleRetryWithFit(fitInfo, compositeLabel);
29044
- }, [handleRetryWithFit, selectedFile, setPendingCustomSizeBySection]);
28941
+ if (sizingResult.found === false) return;
28942
+ if (noFitFoundRef.current) return;
28943
+ if (autoTryOnFiredRef.current) return;
28944
+ if (tryOnProcessing || resultImageUrl) return;
28945
+ if (!selectedFile && !selectedFileRef.current) return;
28946
+ autoTryOnFiredRef.current = true;
28947
+ handleTryOnSubmit();
28948
+ }, [sizingResult, tryOnProcessing, resultImageUrl, selectedFile, handleTryOnSubmit]);
29045
28949
  const handleDownload = reactExports.useCallback(() => {
29046
28950
  if (!resultImageUrl) return;
29047
28951
  if (resultImageUrl.startsWith("data:")) {
@@ -29483,15 +29387,13 @@ function PrimeStyleTryonInner({
29483
29387
  sizeGuide,
29484
29388
  resultImageUrl,
29485
29389
  productImage,
29390
+ productImages,
29486
29391
  productTitle,
29487
29392
  productMaterial,
29488
29393
  productDescription,
29489
29394
  sizingUnit,
29490
29395
  setView,
29491
29396
  handleDownload,
29492
- onRetryWithFit: handleRetryWithFit,
29493
- retryLoading,
29494
- retryStartedAt,
29495
29397
  selectedFile,
29496
29398
  previewUrl,
29497
29399
  handleFileSelect,
@@ -29504,9 +29406,6 @@ function PrimeStyleTryonInner({
29504
29406
  measurementType: detectMeasurementType(productTitle),
29505
29407
  activeSection,
29506
29408
  setActiveSection,
29507
- pendingCustomSizes,
29508
- onPendingCustomSizeChange: setPendingCustomSizeBySection,
29509
- onRegenerateTryOn: handleRegenerateTryOn,
29510
29409
  onResetTryOn: () => {
29511
29410
  setSelectedFile(null);
29512
29411
  if (previewUrl) URL.revokeObjectURL(previewUrl);