@primestyleai/tryon 5.10.101 → 5.10.103

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
@@ -11328,6 +11330,37 @@ const STYLES$1 = `
11328
11330
  border: 1px solid rgba(33,84,239,0.25); border-radius: 2vw;
11329
11331
  padding: 0.1vw 0.5vw;
11330
11332
  }
11333
+ .ps-tryon-sr-card-v2-rec-pill {
11334
+ align-self: flex-start;
11335
+ font-size: 0.55vw; font-weight: 700; color: var(--ps-accent);
11336
+ text-transform: uppercase; letter-spacing: 0.08em;
11337
+ background: rgba(33, 84, 239, 0.10);
11338
+ border: 1px solid rgba(33, 84, 239, 0.18);
11339
+ border-radius: 2vw;
11340
+ padding: 0.18vw 0.6vw;
11341
+ margin-top: 0.3vw;
11342
+ }
11343
+ .ps-tryon-sr-card-v2-rec-pill.is-overridden {
11344
+ color: #b45309;
11345
+ background: rgba(180, 83, 9, 0.10);
11346
+ border-color: rgba(180, 83, 9, 0.25);
11347
+ }
11348
+ .ps-tryon-sr-card-v2-view {
11349
+ align-self: center;
11350
+ margin-top: 0.4vw;
11351
+ font-size: 0.62vw; font-weight: 600;
11352
+ color: var(--ps-accent);
11353
+ text-transform: uppercase; letter-spacing: 0.06em;
11354
+ display: inline-flex; align-items: center; justify-content: center; gap: 0.2vw;
11355
+ transition: gap 0.2s ease;
11356
+ }
11357
+ .ps-tryon-sr-card-v2:hover .ps-tryon-sr-card-v2-view {
11358
+ gap: 0.4vw;
11359
+ }
11360
+ .ps-tryon-sr-card-v2-view > span {
11361
+ font-size: 0.85vw; line-height: 1; color: var(--ps-accent);
11362
+ transform: translateY(-0.05vw);
11363
+ }
11331
11364
  .ps-tryon-sr-card-v2-img { display: none; }
11332
11365
  .ps-tryon-sr-card-v2-icon {
11333
11366
  position: absolute; bottom: 0.35vw; right: 0.45vw;
@@ -11391,24 +11424,165 @@ const STYLES$1 = `
11391
11424
  filter: blur(8px) brightness(0.5); transform: scale(1.05);
11392
11425
  transition: filter 0.5s ease, transform 0.5s ease;
11393
11426
  }
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);
11427
+ /* ── Try-on generation badge (left-side overlay during VTO) ── */
11428
+ .ps-tryon-badge {
11429
+ position: absolute; bottom: 16px; left: 16px; right: 16px;
11430
+ z-index: 5;
11431
+ background: #ffffff;
11432
+ border-radius: 14px;
11433
+ padding: 14px 18px;
11434
+ max-width: 420px;
11435
+ box-shadow: 0 8px 28px rgba(20, 30, 60, 0.14), 0 2px 6px rgba(20, 30, 60, 0.08);
11436
+ display: flex; flex-direction: column; gap: 10px;
11437
+ font-family: inherit;
11438
+ pointer-events: none;
11439
+ }
11440
+ .ps-tryon-badge-row {
11441
+ display: flex; align-items: center; gap: 12px;
11442
+ }
11443
+ .ps-tryon-badge-spinner {
11444
+ display: inline-flex; flex-shrink: 0;
11445
+ animation: ps-tryon-badge-spin 1s linear infinite;
11446
+ }
11447
+ .ps-tryon-badge-title {
11448
+ flex: 1; min-width: 0;
11449
+ font-size: 15px; font-weight: 600; color: #0f172a;
11450
+ white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
11451
+ }
11452
+ .ps-tryon-badge-pct {
11453
+ flex-shrink: 0;
11454
+ background: linear-gradient(135deg, var(--ps-accent) 0%, var(--ps-accent-hover) 100%);
11455
+ color: #fff; font-size: 12px; font-weight: 700;
11456
+ padding: 4px 11px; border-radius: 999px;
11457
+ letter-spacing: 0.02em;
11458
+ }
11459
+ .ps-tryon-badge-bar {
11460
+ height: 5px;
11461
+ background: #eef2f8;
11462
+ border-radius: 999px; overflow: hidden;
11463
+ }
11464
+ .ps-tryon-badge-bar-fill {
11465
+ height: 100%;
11466
+ background: linear-gradient(90deg, var(--ps-accent) 0%, var(--ps-accent-hover) 100%);
11467
+ border-radius: 999px;
11468
+ transition: width 0.3s ease;
11469
+ }
11470
+ .ps-tryon-badge-foot {
11471
+ display: flex; align-items: center; justify-content: space-between; gap: 10px;
11472
+ font-size: 12px; color: #6b7280;
11473
+ }
11474
+ .ps-tryon-badge-status {
11475
+ display: inline-flex; align-items: center; gap: 6px;
11476
+ font-weight: 500;
11477
+ white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
11478
+ }
11479
+ .ps-tryon-badge-status-icon {
11480
+ display: inline-flex; flex-shrink: 0; color: var(--ps-accent);
11481
+ }
11482
+ .ps-tryon-badge-eta {
11483
+ display: inline-flex; align-items: center; gap: 5px;
11484
+ font-weight: 500; flex-shrink: 0;
11485
+ }
11486
+ @keyframes ps-tryon-badge-spin {
11487
+ to { transform: rotate(360deg); }
11488
+ }
11489
+
11490
+ /* ── Product photo strip (single-garment, below the size card) ──
11491
+ Three thumbnails per slide, evenly spaced, auto-advances. Lives at
11492
+ the bottom of the right panel as decoration / entertainment. */
11493
+ .ps-tryon-photo-strip {
11494
+ margin-top: 1vw;
11495
+ display: flex; flex-direction: column; gap: 0.6vw;
11496
+ }
11497
+ .ps-tryon-photo-strip-head {
11498
+ display: flex; align-items: center; justify-content: space-between;
11499
+ }
11500
+ .ps-tryon-photo-strip-badge {
11501
+ color: var(--ps-accent);
11502
+ font-size: 0.7vw; font-weight: 600;
11503
+ display: inline-flex; align-items: center; gap: 0.3vw;
11504
+ letter-spacing: 0.04em; text-transform: uppercase;
11505
+ }
11506
+ .ps-tryon-photo-strip-row {
11507
+ display: grid;
11508
+ grid-template-columns: repeat(3, 1fr);
11509
+ gap: 0.6vw;
11510
+ animation: ps-tryon-photo-strip-fade 0.5s ease;
11511
+ }
11512
+ .ps-tryon-photo-strip-cell {
11513
+ position: relative;
11514
+ aspect-ratio: 3 / 4;
11515
+ overflow: hidden;
11516
+ border-radius: 0.5vw;
11517
+ background: #f3f5f9;
11518
+ box-shadow: 0 0.15vw 0.5vw rgba(20, 30, 60, 0.08);
11519
+ transition: transform 0.25s ease;
11520
+ }
11521
+ .ps-tryon-photo-strip-cell:hover {
11522
+ transform: translateY(-0.15vw);
11523
+ }
11524
+ .ps-tryon-photo-strip-cell > img {
11525
+ width: 100%; height: 100%;
11526
+ object-fit: cover;
11527
+ user-select: none;
11528
+ pointer-events: none;
11529
+ }
11530
+ .ps-tryon-photo-strip-dots {
11531
+ display: flex; justify-content: center; align-items: center; gap: 0.3vw;
11532
+ padding-top: 0.2vw;
11533
+ }
11534
+ .ps-tryon-photo-strip-dot {
11535
+ width: 0.3vw; height: 0.3vw; min-width: 4px; min-height: 4px;
11536
+ border-radius: 999px;
11537
+ background: #d6dbe4;
11538
+ transition: background-color 0.3s ease, width 0.3s ease;
11539
+ }
11540
+ .ps-tryon-photo-strip-dot.is-active {
11541
+ background: var(--ps-accent);
11542
+ width: 0.9vw; min-width: 14px;
11543
+ }
11544
+ @keyframes ps-tryon-photo-strip-fade {
11545
+ from { opacity: 0; transform: translateY(0.3vw); }
11546
+ to { opacity: 1; transform: translateY(0); }
11547
+ }
11548
+
11549
+ /* ── No-fit empty state (single-garment, sizing match% < 50%) ── */
11550
+ .ps-tryon-nofit {
11551
+ background: #ffffff;
11552
+ border: 1px solid #eef2f8;
11553
+ border-radius: 1vw;
11554
+ padding: 3vw 2.4vw 2.6vw;
11555
+ text-align: center;
11556
+ display: flex; flex-direction: column; align-items: center;
11557
+ box-shadow: 0 0.4vw 1.2vw rgba(20, 30, 60, 0.05);
11403
11558
  }
11404
- .ps-tryon-v2-processing-label > span:first-child {
11405
- animation: ps-loading-pulse 2s ease-in-out infinite;
11559
+ .ps-tryon-nofit-icon {
11560
+ width: 4.4vw; height: 4.4vw; min-width: 56px; min-height: 56px;
11561
+ border-radius: 50%;
11562
+ background: #fef2f2;
11563
+ color: #dc2626;
11564
+ display: flex; align-items: center; justify-content: center;
11565
+ margin-bottom: 1.2vw;
11566
+ }
11567
+ .ps-tryon-nofit-icon svg { width: 50%; height: 50%; }
11568
+ .ps-tryon-nofit-title {
11569
+ font-size: 1.15vw; font-weight: 700; color: #0f172a;
11570
+ margin: 0 0 0.7vw;
11571
+ line-height: 1.3;
11572
+ letter-spacing: -0.01em;
11573
+ }
11574
+ .ps-tryon-nofit-sub {
11575
+ font-size: 0.78vw; color: #6b7280;
11576
+ margin: 0 0 2vw;
11577
+ max-width: 22vw;
11578
+ line-height: 1.6;
11579
+ }
11580
+ .ps-tryon-nofit-actions {
11581
+ display: flex; align-items: center; justify-content: space-between;
11582
+ width: 100%; gap: 1vw;
11583
+ padding-top: 1.2vw;
11584
+ border-top: 1px solid #f1f4fa;
11406
11585
  }
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
11586
 
11413
11587
  /* "I don't know" link */
11414
11588
  .ps-tryon-v2-dontknow {
@@ -18741,140 +18915,6 @@ function MobileBottomTabs({ mode, onSwitchToManual, onSwitchToScan, t: t2 }) {
18741
18915
  )
18742
18916
  ] });
18743
18917
  }
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 aiFacts = [
18783
- t2("Our model is analyzing 150+ body landmarks for the perfect fit"),
18784
- t2("Calibrating fabric drape against your body proportions"),
18785
- t2("Cross-checking fit against millions of garment patterns"),
18786
- t2("Rendering shadows and highlights to match your photo's lighting")
18787
- ];
18788
- const styleTips = [
18789
- t2("Pair this with neutral tones for a balanced look"),
18790
- t2("Layer with a fitted base for sharper silhouette"),
18791
- t2("Cuff once for a relaxed everyday feel")
18792
- ];
18793
- const [factIdx, setFactIdx] = reactExports.useState(0);
18794
- const [tipIdx, setTipIdx] = reactExports.useState(0);
18795
- reactExports.useEffect(() => {
18796
- const f2 = setInterval(() => setFactIdx((i) => (i + 1) % aiFacts.length), 4e3);
18797
- const s = setInterval(() => setTipIdx((i) => (i + 1) % styleTips.length), 5e3);
18798
- return () => {
18799
- clearInterval(f2);
18800
- clearInterval(s);
18801
- };
18802
- }, []);
18803
- const garmentLine = productMaterial?.trim() || (productDescription?.trim() ? productDescription.trim().slice(0, 90) + (productDescription.trim().length > 90 ? "…" : "") : null) || t2("Crafted with quality materials for everyday comfort");
18804
- const Panel = /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-tryon-processing-v2-panel", children: [
18805
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-tryon-processing-v2-head", children: [
18806
- /* @__PURE__ */ jsxRuntimeExports.jsx("h3", { className: "ps-tryon-processing-v2-title", children: t2("Generating Your Look") }),
18807
- /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "ps-tryon-processing-v2-sub", children: t2("Our AI is precisely mapping the garment to your unique proportions.") })
18808
- ] }),
18809
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-tryon-processing-v2-ring-wrap", children: [
18810
- /* @__PURE__ */ jsxRuntimeExports.jsxs("svg", { viewBox: "0 0 96 96", width: "120", height: "120", "aria-hidden": "true", children: [
18811
- /* @__PURE__ */ jsxRuntimeExports.jsx("circle", { cx: "48", cy: "48", r: RING_RADIUS$1, className: "ps-tryon-progress-ring-track" }),
18812
- /* @__PURE__ */ jsxRuntimeExports.jsx(
18813
- "circle",
18814
- {
18815
- ref: ringRef,
18816
- cx: "48",
18817
- cy: "48",
18818
- r: RING_RADIUS$1,
18819
- className: "ps-tryon-progress-ring-fill",
18820
- strokeDasharray: RING_CIRC,
18821
- strokeDashoffset: RING_CIRC
18822
- }
18823
- )
18824
- ] }),
18825
- /* @__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%" }) })
18826
- ] }),
18827
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { ref: statusRef, className: "ps-tryon-processing-v2-status", children: statuses[0] }),
18828
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-tryon-processing-v2-sep" }),
18829
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
18830
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-tryon-processing-v2-section-label", children: t2("WHILE YOU WAIT") }),
18831
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-tryon-processing-v2-cards", children: [
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-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" }) }) }),
18834
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-tryon-processing-v2-card-text", children: [
18835
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-tryon-processing-v2-card-head", children: t2("Style Tip") }),
18836
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-tryon-processing-v2-card-body", children: styleTips[tipIdx] }, tipIdx)
18837
- ] })
18838
- ] }),
18839
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-tryon-processing-v2-card", children: [
18840
- /* @__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: [
18841
- /* @__PURE__ */ jsxRuntimeExports.jsx("circle", { cx: "12", cy: "12", r: "10" }),
18842
- /* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: "12", y1: "8", x2: "12", y2: "12" }),
18843
- /* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: "12", y1: "16", x2: "12.01", y2: "16" })
18844
- ] }) }),
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("Garment Spotlight") }),
18847
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-tryon-processing-v2-card-body", children: garmentLine })
18848
- ] })
18849
- ] }),
18850
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-tryon-processing-v2-card", children: [
18851
- /* @__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" }) }) }),
18852
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-tryon-processing-v2-card-text", children: [
18853
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-tryon-processing-v2-card-head", children: t2("AI Fact") }),
18854
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-tryon-processing-v2-card-body", children: aiFacts[factIdx] }, factIdx)
18855
- ] })
18856
- ] })
18857
- ] })
18858
- ] }),
18859
- onCancel && /* @__PURE__ */ jsxRuntimeExports.jsx("button", { type: "button", className: "ps-tryon-processing-v2-cancel", onClick: onCancel, children: t2("Cancel Generation") })
18860
- ] });
18861
- if (variant === "panel-only") return Panel;
18862
- return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-tryon-processing-v2", children: [
18863
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-tryon-processing-v2-image", children: [
18864
- previewUrl && /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
18865
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-tryon-processing-blur", style: { backgroundImage: `url(${previewUrl})` } }),
18866
- /* @__PURE__ */ jsxRuntimeExports.jsx("img", { src: previewUrl, alt: t2("Your photo"), className: "ps-tryon-processing-model" })
18867
- ] }),
18868
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-tryon-scan-line" }),
18869
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-tryon-scan-overlay" }),
18870
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-tryon-processing-v2-badge", children: [
18871
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "ps-tryon-processing-v2-badge-dot", "aria-hidden": "true" }),
18872
- t2("ANALYZING BODY MAP")
18873
- ] })
18874
- ] }),
18875
- Panel
18876
- ] });
18877
- }
18878
18918
  const SKELETON_CONNECTIONS$1 = [
18879
18919
  ["leftShoulder", "rightShoulder"],
18880
18920
  ["leftShoulder", "leftElbow"],
@@ -18960,30 +19000,6 @@ function MobileScanningView({
18960
19000
  onSwitchToManual,
18961
19001
  t: t2
18962
19002
  }) {
18963
- if (tryOnProcessing) {
18964
- return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-msc-root", children: [
18965
- /* @__PURE__ */ jsxRuntimeExports.jsx(
18966
- EngagingTryOnView,
18967
- {
18968
- previewUrl,
18969
- productMaterial,
18970
- productDescription,
18971
- onCancel: onCancelTryOn,
18972
- t: t2
18973
- }
18974
- ),
18975
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-bpm-bottom", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
18976
- MobileBottomTabs,
18977
- {
18978
- mode: "scan",
18979
- onSwitchToManual,
18980
- onSwitchToScan: () => {
18981
- },
18982
- t: t2
18983
- }
18984
- ) })
18985
- ] });
18986
- }
18987
19003
  const displayImage = previewUrl || productImage || "";
18988
19004
  const isPhotoMode = !!previewUrl;
18989
19005
  const stages = isPhotoMode ? [
@@ -19003,11 +19019,18 @@ function MobileScanningView({
19003
19019
  const img = e.currentTarget;
19004
19020
  setDims({ w: img.naturalWidth || img.offsetWidth, h: img.naturalHeight || img.offsetHeight });
19005
19021
  };
19022
+ const TOTAL_MS = 6e3;
19023
+ const LAST_HOLD_MS = 1e3;
19024
+ const startRef = reactExports.useRef(Date.now());
19006
19025
  const [stageIdx, setStageIdx] = reactExports.useState(0);
19007
19026
  reactExports.useEffect(() => {
19027
+ const stepMs = (TOTAL_MS - LAST_HOLD_MS) / Math.max(1, stages.length - 1);
19008
19028
  const id2 = setInterval(() => {
19009
- setStageIdx((i) => (i + 1) % stages.length);
19010
- }, 1500);
19029
+ const elapsed = Date.now() - startRef.current;
19030
+ const idx = Math.min(stages.length - 1, Math.floor(elapsed / stepMs));
19031
+ setStageIdx((prev) => prev === idx ? prev : idx);
19032
+ if (idx >= stages.length - 1) clearInterval(id2);
19033
+ }, 100);
19011
19034
  return () => clearInterval(id2);
19012
19035
  }, [stages.length]);
19013
19036
  reactExports.useEffect(() => {
@@ -19242,6 +19265,132 @@ function MultiSectionMobile({
19242
19265
  sizeGuide ? null : null
19243
19266
  ] });
19244
19267
  }
19268
+ const TARGET_SECONDS = 22;
19269
+ function TryOnGenerationBadge({
19270
+ tryOnStartedAt,
19271
+ t: t2
19272
+ }) {
19273
+ const [, force] = reactExports.useState(0);
19274
+ reactExports.useEffect(() => {
19275
+ if (tryOnStartedAt == null) return;
19276
+ const id2 = setInterval(() => force((v2) => v2 + 1), 200);
19277
+ return () => clearInterval(id2);
19278
+ }, [tryOnStartedAt]);
19279
+ if (tryOnStartedAt == null) return null;
19280
+ const elapsed = (Date.now() - tryOnStartedAt) / 1e3;
19281
+ const pct = Math.min(95, elapsed / TARGET_SECONDS * 100);
19282
+ const pctRounded = Math.round(pct);
19283
+ const remaining = Math.max(0, TARGET_SECONDS - Math.floor(elapsed));
19284
+ const etaText = elapsed >= TARGET_SECONDS ? t2("almost done") : `~${remaining}s ${t2("left")}`;
19285
+ const statuses = [
19286
+ t2("Preparing your image"),
19287
+ t2("Analyzing body proportions"),
19288
+ t2("Mapping garment to body"),
19289
+ t2("Refining drape and shadows"),
19290
+ t2("Almost done")
19291
+ ];
19292
+ const stepIdx = Math.min(
19293
+ statuses.length - 1,
19294
+ Math.floor(elapsed / TARGET_SECONDS * statuses.length)
19295
+ );
19296
+ const status = statuses[stepIdx];
19297
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-tryon-badge", role: "status", "aria-live": "polite", children: [
19298
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-tryon-badge-row", children: [
19299
+ /* @__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: [
19300
+ /* @__PURE__ */ jsxRuntimeExports.jsx("defs", { children: /* @__PURE__ */ jsxRuntimeExports.jsxs("linearGradient", { id: "ps-tryon-badge-grad", x1: "0", y1: "0", x2: "1", y2: "1", children: [
19301
+ /* @__PURE__ */ jsxRuntimeExports.jsx("stop", { offset: "0%", stopColor: "#3B82F6" }),
19302
+ /* @__PURE__ */ jsxRuntimeExports.jsx("stop", { offset: "100%", stopColor: "#2563EB" })
19303
+ ] }) }),
19304
+ /* @__PURE__ */ jsxRuntimeExports.jsx("circle", { cx: "12", cy: "12", r: "9", fill: "none", stroke: "rgba(59,130,246,0.18)", strokeWidth: "2.4" }),
19305
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
19306
+ "circle",
19307
+ {
19308
+ cx: "12",
19309
+ cy: "12",
19310
+ r: "9",
19311
+ fill: "none",
19312
+ stroke: "url(#ps-tryon-badge-grad)",
19313
+ strokeWidth: "2.4",
19314
+ strokeLinecap: "round",
19315
+ strokeDasharray: "56.5",
19316
+ strokeDashoffset: "38"
19317
+ }
19318
+ )
19319
+ ] }) }),
19320
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "ps-tryon-badge-title", children: t2("Generating your look...") }),
19321
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "ps-tryon-badge-pct", children: [
19322
+ pctRounded,
19323
+ "%"
19324
+ ] })
19325
+ ] }),
19326
+ /* @__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}%` } }) }),
19327
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-tryon-badge-foot", children: [
19328
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "ps-tryon-badge-status", children: [
19329
+ /* @__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: [
19330
+ /* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: "6", y1: "10", x2: "6", y2: "14" }),
19331
+ /* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: "10", y1: "6", x2: "10", y2: "18" }),
19332
+ /* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: "14", y1: "9", x2: "14", y2: "15" }),
19333
+ /* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: "18", y1: "11", x2: "18", y2: "13" })
19334
+ ] }) }),
19335
+ status
19336
+ ] }),
19337
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "ps-tryon-badge-eta", children: [
19338
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
19339
+ "svg",
19340
+ {
19341
+ width: "11",
19342
+ height: "11",
19343
+ viewBox: "0 0 24 24",
19344
+ fill: "none",
19345
+ stroke: "currentColor",
19346
+ strokeWidth: "2",
19347
+ strokeLinecap: "round",
19348
+ strokeLinejoin: "round",
19349
+ "aria-hidden": "true",
19350
+ children: [
19351
+ /* @__PURE__ */ jsxRuntimeExports.jsx("circle", { cx: "12", cy: "12", r: "10" }),
19352
+ /* @__PURE__ */ jsxRuntimeExports.jsx("polyline", { points: "12 6 12 12 16 14" })
19353
+ ]
19354
+ }
19355
+ ),
19356
+ etaText
19357
+ ] })
19358
+ ] })
19359
+ ] });
19360
+ }
19361
+ const PER_SLIDE = 3;
19362
+ const CYCLE_MS = 4e3;
19363
+ function ProductPhotoCarouselCard({
19364
+ photos,
19365
+ productTitle,
19366
+ t: t2
19367
+ }) {
19368
+ const [groupIdx, setGroupIdx] = reactExports.useState(0);
19369
+ const totalGroups = Math.max(1, Math.ceil(photos.length / PER_SLIDE));
19370
+ reactExports.useEffect(() => {
19371
+ if (totalGroups < 2) return;
19372
+ const id2 = setInterval(() => setGroupIdx((v2) => (v2 + 1) % totalGroups), CYCLE_MS);
19373
+ return () => clearInterval(id2);
19374
+ }, [totalGroups]);
19375
+ if (!photos || photos.length === 0) return null;
19376
+ const start = groupIdx * PER_SLIDE;
19377
+ const slide = photos.slice(start, start + PER_SLIDE);
19378
+ while (slide.length < PER_SLIDE && photos.length >= PER_SLIDE) {
19379
+ slide.push(photos[(start + slide.length) % photos.length]);
19380
+ }
19381
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-tryon-photo-strip", role: "group", "aria-label": t2("Product photos"), children: [
19382
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-tryon-photo-strip-head", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "ps-tryon-photo-strip-badge", children: [
19383
+ /* @__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: [
19384
+ /* @__PURE__ */ jsxRuntimeExports.jsx("rect", { x: "3", y: "3", width: "18", height: "18", rx: "2" }),
19385
+ /* @__PURE__ */ jsxRuntimeExports.jsx("circle", { cx: "9", cy: "9", r: "2" }),
19386
+ /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21" })
19387
+ ] }),
19388
+ t2("Gallery")
19389
+ ] }) }),
19390
+ /* @__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),
19391
+ 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)) })
19392
+ ] });
19393
+ }
19245
19394
  function garmentIconForSection(name) {
19246
19395
  const n2 = name.toLowerCase();
19247
19396
  if (n2.includes("jacket") || n2.includes("blazer") || n2.includes("coat")) return garmentJacketImg;
@@ -19265,62 +19414,6 @@ const SKELETON_CONNECTIONS = [
19265
19414
  ["rightHip", "rightKnee"],
19266
19415
  ["rightKnee", "rightAnkle"]
19267
19416
  ];
19268
- const TRYON_TARGET_SECONDS = 22;
19269
- const TRYON_RING_RADIUS = 27;
19270
- const TRYON_RING_CIRC = 2 * Math.PI * TRYON_RING_RADIUS;
19271
- function TryOnProgress({ t: t2, isActive }) {
19272
- const startRef = reactExports.useRef(null);
19273
- const ringRef = reactExports.useRef(null);
19274
- const barRef = reactExports.useRef(null);
19275
- const etaRef = reactExports.useRef(null);
19276
- const pctRef = reactExports.useRef(null);
19277
- reactExports.useEffect(() => {
19278
- if (!isActive) {
19279
- startRef.current = null;
19280
- return;
19281
- }
19282
- startRef.current = Date.now();
19283
- const id2 = setInterval(() => {
19284
- const start = startRef.current || Date.now();
19285
- const elapsed = (Date.now() - start) / 1e3;
19286
- const pct = Math.min(95, elapsed / TRYON_TARGET_SECONDS * 100);
19287
- const val = Math.round(pct);
19288
- if (barRef.current) barRef.current.style.width = `${val}%`;
19289
- if (pctRef.current) pctRef.current.textContent = `${val}%`;
19290
- if (ringRef.current) {
19291
- ringRef.current.style.strokeDashoffset = String(TRYON_RING_CIRC * (1 - pct / 100));
19292
- }
19293
- if (etaRef.current) {
19294
- const remaining = Math.max(0, TRYON_TARGET_SECONDS - Math.floor(elapsed));
19295
- etaRef.current.textContent = elapsed >= TRYON_TARGET_SECONDS ? "•••" : `~${remaining}s`;
19296
- }
19297
- }, 200);
19298
- return () => clearInterval(id2);
19299
- }, [isActive]);
19300
- if (!isActive) return null;
19301
- return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-tryon-progress-wrap", children: [
19302
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-tryon-progress-ring", children: [
19303
- /* @__PURE__ */ jsxRuntimeExports.jsxs("svg", { width: "64", height: "64", viewBox: "0 0 64 64", "aria-hidden": "true", children: [
19304
- /* @__PURE__ */ jsxRuntimeExports.jsx("circle", { cx: "32", cy: "32", r: TRYON_RING_RADIUS, className: "ps-tryon-progress-ring-track" }),
19305
- /* @__PURE__ */ jsxRuntimeExports.jsx(
19306
- "circle",
19307
- {
19308
- ref: ringRef,
19309
- cx: "32",
19310
- cy: "32",
19311
- r: TRYON_RING_RADIUS,
19312
- className: "ps-tryon-progress-ring-fill",
19313
- strokeDasharray: TRYON_RING_CIRC,
19314
- strokeDashoffset: TRYON_RING_CIRC
19315
- }
19316
- )
19317
- ] }),
19318
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { ref: etaRef, className: "ps-tryon-progress-eta", children: `~${TRYON_TARGET_SECONDS}s` })
19319
- ] }),
19320
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-tryon-progress-bar-wrap", children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { ref: barRef, className: "ps-tryon-progress-bar-fill" }) }),
19321
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { ref: pctRef, className: "ps-tryon-progress-pct", children: "0%" })
19322
- ] });
19323
- }
19324
19417
  function FaceOverlay({
19325
19418
  landmarks,
19326
19419
  imgWidth,
@@ -19447,14 +19540,20 @@ function StageCycler({
19447
19540
  { title: t2("MATCHING SIZE"), desc: t2("Comparing your measurements to the size guide.") },
19448
19541
  { title: t2("FINALIZING RESULT"), desc: t2("Almost done — preparing your recommendation.") }
19449
19542
  ];
19543
+ const TOTAL_MS = 6e3;
19544
+ const LAST_HOLD_MS = 1e3;
19545
+ const startRef = reactExports.useRef(Date.now());
19450
19546
  const [idx, setIdx] = reactExports.useState(0);
19451
19547
  reactExports.useEffect(() => {
19452
- if (sizingDone) return;
19548
+ const stepMs = (TOTAL_MS - LAST_HOLD_MS) / Math.max(1, sizingStages.length - 1);
19453
19549
  const id2 = setInterval(() => {
19454
- setIdx((i) => Math.min(i + 1, sizingStages.length - 1));
19455
- }, 900);
19550
+ const elapsed = Date.now() - startRef.current;
19551
+ const next = Math.min(sizingStages.length - 1, Math.floor(elapsed / stepMs));
19552
+ setIdx((prev) => prev === next ? prev : next);
19553
+ if (next >= sizingStages.length - 1) clearInterval(id2);
19554
+ }, 100);
19456
19555
  return () => clearInterval(id2);
19457
- }, [sizingDone, sizingStages.length]);
19556
+ }, [sizingStages.length]);
19458
19557
  const current = sizingStages[idx] ?? sizingStages[0];
19459
19558
  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: [
19460
19559
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-msc-stage-title", children: current.title }),
@@ -19728,19 +19827,11 @@ function SectionDetailView({
19728
19827
  internationalSizes,
19729
19828
  continueLabel,
19730
19829
  renderRaw = false,
19731
- sectionFound,
19732
- onSelectForTryOn,
19733
- onRegenerateTryOn,
19734
- pendingOverride,
19735
- retryLoading,
19736
- retryStartedAt
19830
+ sectionFound
19737
19831
  }) {
19738
19832
  const recSize = sectionResult?.recommendedSize || "";
19739
- const [selectedSize, setSelectedSize] = reactExports.useState(
19740
- pendingOverride?.selectedSize && pendingOverride.selectedSize !== (sectionResult?.recommendedSize || "") ? pendingOverride.selectedSize : null
19741
- );
19833
+ const [selectedSize, setSelectedSize] = reactExports.useState(null);
19742
19834
  const tryOnElapsedS = useElapsedSeconds(tryOnStartedAt ?? null);
19743
- const retryElapsedS = useElapsedSeconds(retryStartedAt ?? null);
19744
19835
  const unitLblLower = unitLbl.toLowerCase();
19745
19836
  const displayUnitId = unitLblLower.includes("mm") ? "mm" : unitLblLower.includes("cm") ? "cm" : "in";
19746
19837
  const fromUnit = chartUnit || displayUnitId;
@@ -19753,9 +19844,7 @@ function SectionDetailView({
19753
19844
  const countryOptions = internationalSizes ? Object.keys(internationalSizes) : [];
19754
19845
  const [selectedCountry, setSelectedCountry] = reactExports.useState(null);
19755
19846
  const recLength = lengthEntry?.secResult?.recommendedSize || "";
19756
- const [selectedLength, setSelectedLength] = reactExports.useState(
19757
- pendingOverride?.selectedLength && pendingOverride.selectedLength !== recLength ? pendingOverride.selectedLength : null
19758
- );
19847
+ const [selectedLength, setSelectedLength] = reactExports.useState(null);
19759
19848
  const lengthSizes = reactExports.useMemo(() => {
19760
19849
  if (!lengthEntry) return [];
19761
19850
  const sec = lengthEntry.section;
@@ -19982,8 +20071,8 @@ function SectionDetailView({
19982
20071
  const userInColUnit = colIsCm && userIsInches ? +(userNum2 * 2.54).toFixed(1) : !colIsCm && !userIsInches ? +(userNum2 * 2.54).toFixed(1) : userNum2;
19983
20072
  const range2 = rMaxRaw - rMinRaw;
19984
20073
  const threshold2 = range2 > 0 ? range2 * 0.5 : rMinRaw * 0.05 || 3;
19985
- const tol2 = Math.max((rMaxRaw || rMinRaw) * 5e-3, 0.25);
19986
- if (userInColUnit >= rMinRaw - tol2 && userInColUnit <= rMaxRaw + tol2) fit2 = "good";
20074
+ const tol = colIsCm ? 2.54 : 1;
20075
+ if (userInColUnit > rMinRaw - tol && userInColUnit < rMaxRaw + tol) fit2 = "good";
19987
20076
  else if (userInColUnit < rMinRaw) {
19988
20077
  const diff = rMinRaw - userInColUnit;
19989
20078
  fit2 = diff > threshold2 * 2 ? "too-long" : diff > threshold2 ? "long" : "a-bit-long";
@@ -20016,8 +20105,10 @@ function SectionDetailView({
20016
20105
  const measLower = m2.measurement.toLowerCase();
20017
20106
  const isDirectional = /length|inseam|sleeve|hem|rise/.test(measLower);
20018
20107
  let fit;
20019
- const tol = Math.max((rMax || rMin) * 0.03, 0.5);
20020
- if (userNum >= rMin - tol && userNum <= rMax + tol) {
20108
+ const perfectTol = chartUnit === "cm" ? 2.54 : chartUnit === "mm" ? 25.4 : 1;
20109
+ const lowBound = rMin - perfectTol;
20110
+ const highBound = rMax + perfectTol;
20111
+ if (userNum > lowBound && userNum < highBound) {
20021
20112
  fit = "good";
20022
20113
  } else if (isDirectional) {
20023
20114
  const diff = userNum > rMax ? userNum - rMax : rMin - userNum;
@@ -20064,65 +20155,6 @@ function SectionDetailView({
20064
20155
  const start = Math.max(0, Math.min(lengthOptions.length - 3, idx - 1));
20065
20156
  return lengthOptions.slice(start, start + 3);
20066
20157
  })();
20067
- const autoCommitInitialMount = reactExports.useRef(true);
20068
- reactExports.useLayoutEffect(() => {
20069
- if (autoCommitInitialMount.current) {
20070
- autoCommitInitialMount.current = false;
20071
- return;
20072
- }
20073
- if (!onSelectForTryOn) return;
20074
- const effSize = displaySize;
20075
- const effLength = selectedLength || backendLength || "";
20076
- const hasSizePick = !!selectedSize && selectedSize !== recSize;
20077
- const hasLengthPick = !!selectedLength && selectedLength !== backendLength;
20078
- if (!hasSizePick && !hasLengthPick) {
20079
- onSelectForTryOn(sectionName, null);
20080
- return;
20081
- }
20082
- const mainDetails = sectionResult?.matchDetails || [];
20083
- const lengthMeasurements = new Set(
20084
- (lengthEntry?.secResult?.matchDetails || []).map((m2) => m2.measurement.toLowerCase())
20085
- );
20086
- const overrideMd = mainDetails.length ? mainDetails.map((m2) => {
20087
- const measLc = m2.measurement.toLowerCase();
20088
- if (lengthMeasurements.has(measLc) && lengthEntry) {
20089
- const sec = lengthEntry.section;
20090
- const sizeCol = sec.headers.findIndex((h) => /size|length/i.test(h.trim()));
20091
- const sIdx = sizeCol >= 0 ? sizeCol : 0;
20092
- const targetCol = sec.headers.findIndex((h) => {
20093
- const hLc = h.toLowerCase().replace(/\s*\(.*?\)\s*/g, "").trim();
20094
- return !!hLc && (hLc === measLc || hLc.includes(measLc) || measLc.includes(hLc));
20095
- });
20096
- if (targetCol < 0) return m2;
20097
- const row = sec.rows.find((r2) => cellValFn(r2, sIdx, sec.headers[sIdx]) === effLength);
20098
- if (!row) return m2;
20099
- const cell = cellValFn(row, targetCol, sec.headers[targetCol]);
20100
- return cell ? { ...m2, chartRange: cell } : m2;
20101
- }
20102
- const alt = chartRangeFor(m2.measurement, effSize);
20103
- if (alt?.range) return { ...m2, chartRange: alt.range };
20104
- return m2;
20105
- }) : void 0;
20106
- const label = effLength ? `${effSize} / ${effLength}` : effSize;
20107
- console.log(`[ps-sdk:auto-commit] section="${sectionName}" label="${label}"`, {
20108
- selectedSize,
20109
- selectedLength,
20110
- recSize,
20111
- backendLength,
20112
- overrideMdPreview: overrideMd?.map((m2) => ({
20113
- area: m2.measurement,
20114
- userValue: m2.userValue,
20115
- chartRange: m2.chartRange
20116
- }))
20117
- });
20118
- onSelectForTryOn(sectionName, {
20119
- sectionName,
20120
- selectedSize: effSize,
20121
- selectedLength: hasLengthPick ? effLength : void 0,
20122
- displayLabel: label,
20123
- matchDetails: overrideMd
20124
- });
20125
- }, [selectedSize, selectedLength]);
20126
20158
  if (isMobileProp) {
20127
20159
  const cleanSectionName = sectionName.replace(/\s*[—–-]\s*.*/g, "");
20128
20160
  const measurementDesc = (area) => {
@@ -20606,169 +20638,7 @@ function SectionDetailView({
20606
20638
  s
20607
20639
  );
20608
20640
  }) })
20609
- ] }),
20610
- (onRegenerateTryOn || onSelectForTryOn && (!isRecommended || selectedLength && selectedLength !== backendLength)) && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { style: { marginTop: "0.6vw", marginBottom: "0.2vw" }, children: (() => {
20611
- const effSize = displaySize;
20612
- const effLength = selectedLength || backendLength || "";
20613
- const hasLengthPick = !!(selectedLength && selectedLength !== backendLength);
20614
- const label = effLength ? `${effSize} / ${effLength}` : effSize;
20615
- const buildOverrideMatchDetails = () => {
20616
- const mainDetails = sectionResult?.matchDetails || [];
20617
- if (!mainDetails.length) return void 0;
20618
- const lengthMeasurements = new Set(
20619
- (lengthEntry?.secResult?.matchDetails || []).map((m2) => m2.measurement.toLowerCase())
20620
- );
20621
- const out = mainDetails.map((m2) => {
20622
- const measLc = m2.measurement.toLowerCase();
20623
- if (lengthMeasurements.has(measLc) && lengthEntry) {
20624
- const numericPick = effLength && !Number.isNaN(parseFloat(effLength)) ? effLength : null;
20625
- if (numericPick) {
20626
- return { ...m2, chartRange: numericPick };
20627
- }
20628
- const sec = lengthEntry.section;
20629
- const sizeCol = sec.headers.findIndex((h) => /size|length/i.test(h.trim()));
20630
- const sIdx = sizeCol >= 0 ? sizeCol : 0;
20631
- const targetCol = sec.headers.findIndex((h) => {
20632
- const hLc = h.toLowerCase().replace(/\s*\(.*?\)\s*/g, "").trim();
20633
- return !!hLc && (hLc === measLc || hLc.includes(measLc) || measLc.includes(hLc));
20634
- });
20635
- if (targetCol < 0) return m2;
20636
- const row = sec.rows.find((r2) => cellValFn(r2, sIdx, sec.headers[sIdx]) === effLength);
20637
- if (!row) return m2;
20638
- const cell = cellValFn(row, targetCol, sec.headers[targetCol]);
20639
- return cell ? { ...m2, chartRange: cell } : m2;
20640
- }
20641
- const alt = chartRangeFor(m2.measurement, effSize);
20642
- if (alt?.range) return { ...m2, chartRange: alt.range };
20643
- return m2;
20644
- });
20645
- if (effLength && Number.isNaN(parseFloat(effLength))) {
20646
- out.push({
20647
- measurement: "Length",
20648
- userValue: "",
20649
- chartRange: effLength,
20650
- fit: "good"
20651
- });
20652
- }
20653
- return out;
20654
- };
20655
- const isStored = pendingOverride?.selectedSize === effSize;
20656
- if (onRegenerateTryOn) {
20657
- const isRegenerating = !!retryLoading;
20658
- const TARGET_S = 22;
20659
- const countdownS = isRegenerating ? Math.max(0, TARGET_S - retryElapsedS) : TARGET_S;
20660
- return /* @__PURE__ */ jsxRuntimeExports.jsxs(
20661
- "button",
20662
- {
20663
- type: "button",
20664
- "data-ps-regen": "1",
20665
- disabled: isRegenerating,
20666
- onClick: () => {
20667
- const md2 = buildOverrideMatchDetails();
20668
- onRegenerateTryOn({
20669
- sectionName,
20670
- selectedSize: effSize,
20671
- selectedLength: hasLengthPick ? effLength : void 0,
20672
- displayLabel: label,
20673
- matchDetails: md2
20674
- });
20675
- },
20676
- style: {
20677
- width: "100%",
20678
- padding: "0.55vw 0.8vw",
20679
- borderRadius: "0.45vw",
20680
- fontSize: "0.7vw",
20681
- fontWeight: 700,
20682
- background: "var(--ps-accent)",
20683
- color: "#FFFFFF",
20684
- border: "1.5px solid var(--ps-accent)",
20685
- cursor: isRegenerating ? "progress" : "pointer",
20686
- fontFamily: "inherit",
20687
- display: "flex",
20688
- alignItems: "center",
20689
- justifyContent: "center",
20690
- gap: "0.4vw",
20691
- position: "relative",
20692
- overflow: "hidden"
20693
- },
20694
- children: [
20695
- isRegenerating && /* @__PURE__ */ jsxRuntimeExports.jsx(
20696
- "div",
20697
- {
20698
- className: "ps-tryon-regen-fill",
20699
- "aria-hidden": "true"
20700
- },
20701
- retryStartedAt ?? "regen"
20702
- ),
20703
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { style: { position: "relative", zIndex: 1 }, children: isRegenerating ? /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
20704
- t2("Generating new try-on…"),
20705
- " ",
20706
- countdownS,
20707
- "s"
20708
- ] }) : /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
20709
- t2("Try It On"),
20710
- " ",
20711
- /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { style: { fontSize: "0.65vw", opacity: 0.9 }, children: [
20712
- "(",
20713
- label,
20714
- ")"
20715
- ] })
20716
- ] }) })
20717
- ]
20718
- }
20719
- );
20720
- }
20721
- return /* @__PURE__ */ jsxRuntimeExports.jsx(
20722
- "button",
20723
- {
20724
- type: "button",
20725
- onClick: () => {
20726
- if (!onSelectForTryOn) return;
20727
- onSelectForTryOn(sectionName, isStored ? null : {
20728
- sectionName,
20729
- selectedSize: effSize,
20730
- selectedLength: hasLengthPick ? effLength : void 0,
20731
- displayLabel: label,
20732
- matchDetails: buildOverrideMatchDetails()
20733
- });
20734
- },
20735
- style: {
20736
- width: "100%",
20737
- padding: "0.55vw 0.8vw",
20738
- borderRadius: "0.45vw",
20739
- fontSize: "0.7vw",
20740
- fontWeight: 700,
20741
- background: isStored ? "var(--ps-accent)" : "transparent",
20742
- color: isStored ? "#FFFFFF" : "var(--ps-accent)",
20743
- border: `1.5px ${isStored ? "solid" : "dashed"} var(--ps-accent)`,
20744
- cursor: "pointer",
20745
- fontFamily: "inherit",
20746
- display: "flex",
20747
- alignItems: "center",
20748
- justifyContent: "center",
20749
- gap: "0.4vw",
20750
- transition: "background 0.15s, color 0.15s"
20751
- },
20752
- children: isStored ? /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
20753
- t2("Will use this on Try On"),
20754
- " ",
20755
- /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { style: { fontSize: "0.65vw", opacity: 0.9 }, children: [
20756
- "(",
20757
- label,
20758
- ") ✓"
20759
- ] })
20760
- ] }) : /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
20761
- t2("Use this for Try On"),
20762
- " ",
20763
- /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { style: { fontSize: "0.65vw", opacity: 0.75 }, children: [
20764
- "(",
20765
- label,
20766
- ")"
20767
- ] })
20768
- ] })
20769
- }
20770
- );
20771
- })() })
20641
+ ] })
20772
20642
  ] }) }),
20773
20643
  /* @__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: [
20774
20644
  /* @__PURE__ */ jsxRuntimeExports.jsxs("button", { className: "ps-bp-back-btn", onClick: onBack, type: "button", style: { fontSize: "0.7vw" }, children: [
@@ -20844,15 +20714,13 @@ function SizeResultView({
20844
20714
  sizeGuide,
20845
20715
  resultImageUrl,
20846
20716
  productImage,
20717
+ productImages,
20847
20718
  productTitle,
20848
20719
  productMaterial,
20849
20720
  productDescription,
20850
20721
  sizingUnit,
20851
20722
  setView,
20852
20723
  handleDownload,
20853
- onRetryWithFit,
20854
- retryLoading,
20855
- retryStartedAt,
20856
20724
  selectedFile,
20857
20725
  previewUrl,
20858
20726
  handleFileSelect,
@@ -20871,7 +20739,6 @@ function SizeResultView({
20871
20739
  userHeightCm,
20872
20740
  pendingCustomSizes: pendingCustomSizesProp,
20873
20741
  onPendingCustomSizeChange,
20874
- onRegenerateTryOn,
20875
20742
  t: t2
20876
20743
  }) {
20877
20744
  const resultUnitRaw = (sizingResult?.unit || sizingUnit || "").toString().toLowerCase();
@@ -20977,9 +20844,6 @@ function SizeResultView({
20977
20844
  const [poseReady, setPoseReady] = reactExports.useState(false);
20978
20845
  const [imgDims, setImgDims] = reactExports.useState({ w: 800, h: 1200 });
20979
20846
  const pendingCustomSizes = pendingCustomSizesProp ?? {};
20980
- const setPendingCustomSize = (sectionName, override) => {
20981
- onPendingCustomSizeChange?.(sectionName, override);
20982
- };
20983
20847
  const handleImgLoad = reactExports.useCallback((e) => {
20984
20848
  const el2 = e.currentTarget;
20985
20849
  if (el2.naturalWidth && el2.naturalHeight) {
@@ -21148,7 +21012,7 @@ function SizeResultView({
21148
21012
  const prettyLength = lengthRec.replace(/\s+/g, " ").trim();
21149
21013
  return `${baseSize} / ${prettyLength}`;
21150
21014
  }, [lengthEntries, allSectionEntries]);
21151
- const heightLineLabel = reactExports.useMemo(() => {
21015
+ reactExports.useMemo(() => {
21152
21016
  const cm = userHeightCm || 0;
21153
21017
  if (!cm) return "";
21154
21018
  if (unitLbl === "in") {
@@ -21160,8 +21024,8 @@ function SizeResultView({
21160
21024
  return `${Math.round(cm)} cm`;
21161
21025
  }, [userHeightCm, unitLbl]);
21162
21026
  const hasPhoto = !!previewUrl;
21163
- const isSnapProcessing = hasPhoto && (tryOnProcessing || sizingLoading && !sizingResult);
21164
- const isSizingOnly = !hasPhoto && sizingLoading && !sizingResult;
21027
+ const isSnapProcessing = hasPhoto && sizingLoading;
21028
+ const isSizingOnly = !hasPhoto && sizingLoading;
21165
21029
  const sizingDone = !!sizingResult;
21166
21030
  const tryOnDone = !!resultImageUrl && !tryOnProcessing;
21167
21031
  const allDone = hasPhoto ? sizingDone && tryOnDone : sizingDone;
@@ -21228,20 +21092,12 @@ function SizeResultView({
21228
21092
  onLoad: handleImgLoad
21229
21093
  }
21230
21094
  ),
21231
- 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 })
21095
+ 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 })
21232
21096
  ] }),
21233
21097
  (() => {
21234
21098
  const isFaceCategory = measurementType === "face" || measurementType === "head";
21235
21099
  isFaceCategory ? measurementType === "head" ? t2("Detecting head") : t2("Detecting face") : t2("Detecting body pose");
21236
- 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(
21237
- EngagingTryOnView,
21238
- {
21239
- productMaterial,
21240
- productDescription,
21241
- variant: "panel-only",
21242
- t: t2
21243
- }
21244
- ) : /* @__PURE__ */ jsxRuntimeExports.jsx(
21100
+ return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-tryon-sr-right-col", style: { display: "flex", alignItems: "center", justifyContent: "center" }, children: /* @__PURE__ */ jsxRuntimeExports.jsx(
21245
21101
  StageCycler,
21246
21102
  {
21247
21103
  category: isFaceCategory ? measurementType : "body",
@@ -21279,11 +21135,6 @@ function SizeResultView({
21279
21135
  })(),
21280
21136
  onBack: () => setActiveSection(null),
21281
21137
  internationalSizes: entry.secResult?.internationalSizes,
21282
- onSelectForTryOn: setPendingCustomSize,
21283
- onRegenerateTryOn: isAccessory ? void 0 : resultImageUrl && onRegenerateTryOn ? onRegenerateTryOn : void 0,
21284
- retryLoading,
21285
- retryStartedAt,
21286
- pendingOverride: pendingCustomSizes[entry.name] ?? null,
21287
21138
  productImage: resultImageUrl || productImage,
21288
21139
  productTitle,
21289
21140
  isMobile: true,
@@ -21321,10 +21172,7 @@ function SizeResultView({
21321
21172
  ] });
21322
21173
  }
21323
21174
  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-tryon-v2", children: [
21324
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-tryon-v2-bg", style: { position: "relative" }, children: [
21325
- /* @__PURE__ */ jsxRuntimeExports.jsx("img", { src: resultImageUrl || productImage, alt: productTitle, className: "ps-tryon-v2-bg-img" }),
21326
- /* @__PURE__ */ jsxRuntimeExports.jsx(RegenScanOverlay, { active: !!retryLoading && !!resultImageUrl })
21327
- ] }),
21175
+ /* @__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" }) }),
21328
21176
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-tryon-v2-panel", children: [
21329
21177
  mismatchNotice,
21330
21178
  /* @__PURE__ */ jsxRuntimeExports.jsx(
@@ -21346,11 +21194,6 @@ function SizeResultView({
21346
21194
  })(),
21347
21195
  onBack: () => setActiveSection(null),
21348
21196
  internationalSizes: entry.secResult?.internationalSizes,
21349
- onSelectForTryOn: setPendingCustomSize,
21350
- onRegenerateTryOn: isAccessory ? void 0 : resultImageUrl && onRegenerateTryOn ? onRegenerateTryOn : void 0,
21351
- retryLoading,
21352
- retryStartedAt,
21353
- pendingOverride: pendingCustomSizes[entry.name] ?? null,
21354
21197
  t: t2
21355
21198
  }
21356
21199
  )
@@ -21420,11 +21263,8 @@ function SizeResultView({
21420
21263
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-tryon-v2", children: [
21421
21264
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-tryon-v2-bg", style: { position: "relative" }, children: [
21422
21265
  /* @__PURE__ */ jsxRuntimeExports.jsx("img", { src: tryOnProcessing && previewUrl ? previewUrl : resultImageUrl || productImage, alt: productTitle, className: "ps-tryon-v2-bg-img", onLoad: handleImgLoad }),
21423
- tryOnProcessing && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-tryon-v2-processing-label", children: [
21424
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: t2("Generating try-on...") }),
21425
- /* @__PURE__ */ jsxRuntimeExports.jsx(TryOnProgress, { t: t2, isActive: true })
21426
- ] }),
21427
- /* @__PURE__ */ jsxRuntimeExports.jsx(RegenScanOverlay, { active: !!retryLoading && !!resultImageUrl && !tryOnProcessing || !!tryOnProcessing }),
21266
+ tryOnProcessing && /* @__PURE__ */ jsxRuntimeExports.jsx(TryOnGenerationBadge, { tryOnStartedAt: tryOnStartedAt ?? null, t: t2 }),
21267
+ /* @__PURE__ */ jsxRuntimeExports.jsx(RegenScanOverlay, { active: !!tryOnProcessing }),
21428
21268
  resultImageUrl && !tryOnProcessing && poseReady && poseLines && /* @__PURE__ */ jsxRuntimeExports.jsx(MeasurementOverlay, { lines: poseLines, fitRows: (() => {
21429
21269
  const all = [...sizingResult?.matchDetails || []];
21430
21270
  if (sizingResult?.sections) {
@@ -21490,11 +21330,15 @@ function SizeResultView({
21490
21330
  /* @__PURE__ */ jsxRuntimeExports.jsx(
21491
21331
  "span",
21492
21332
  {
21493
- className: "ps-tryon-sr-card-v2-rec",
21494
- style: isOverridden ? { color: "#b45309", fontWeight: 600 } : void 0,
21495
- children: isOverridden ? t2("your selection · not recommended") : heightLineLabel ? `${t2("recommended")} · ${heightLineLabel}` : t2("recommended")
21333
+ className: `ps-tryon-sr-card-v2-rec-pill${isOverridden ? " is-overridden" : ""}`,
21334
+ children: isOverridden ? t2("YOUR SELECTION") : t2("RECOMMENDED")
21496
21335
  }
21497
- )
21336
+ ),
21337
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "ps-tryon-sr-card-v2-view", children: [
21338
+ t2("VIEW DETAILS"),
21339
+ " ",
21340
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { "aria-hidden": "true", children: "›" })
21341
+ ] })
21498
21342
  ] }),
21499
21343
  sectionImg && /* @__PURE__ */ jsxRuntimeExports.jsx("img", { src: sectionImg, alt: name, className: "ps-tryon-sr-card-v2-img" })
21500
21344
  ] }, name);
@@ -21585,11 +21429,6 @@ function SizeResultView({
21585
21429
  },
21586
21430
  backLabel: t2("Back"),
21587
21431
  internationalSizes: sizingResult?.internationalSizes,
21588
- onSelectForTryOn: setPendingCustomSize,
21589
- onRegenerateTryOn: isAccessory ? void 0 : resultImageUrl && onRegenerateTryOn ? onRegenerateTryOn : void 0,
21590
- retryLoading,
21591
- retryStartedAt,
21592
- pendingOverride: pendingCustomSizes[sectionName] ?? null,
21593
21432
  onTryOn: resultImageUrl || vtoExcluded ? void 0 : handleSingleTryOn,
21594
21433
  continueLabel: resultImageUrl ? t2("Continue Shopping") : void 0,
21595
21434
  tryOnProcessing,
@@ -21619,50 +21458,158 @@ function SizeResultView({
21619
21458
  }
21620
21459
  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-tryon-v2", children: [
21621
21460
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-tryon-v2-bg", style: { position: "relative" }, children: [
21622
- /* @__PURE__ */ jsxRuntimeExports.jsx("img", { src: resultImageUrl || productImage, alt: productTitle, className: "ps-tryon-v2-bg-img", onLoad: handleImgLoad }),
21623
- 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 }),
21461
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
21462
+ "img",
21463
+ {
21464
+ src: tryOnProcessing && previewUrl ? previewUrl : resultImageUrl || productImage,
21465
+ alt: productTitle,
21466
+ className: "ps-tryon-v2-bg-img",
21467
+ onLoad: handleImgLoad
21468
+ }
21469
+ ),
21470
+ tryOnProcessing && /* @__PURE__ */ jsxRuntimeExports.jsx(TryOnGenerationBadge, { tryOnStartedAt: tryOnStartedAt ?? null, t: t2 }),
21471
+ /* @__PURE__ */ jsxRuntimeExports.jsx(RegenScanOverlay, { active: !!tryOnProcessing }),
21472
+ 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 }),
21624
21473
  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: [
21625
21474
  !isAccessory && /* @__PURE__ */ jsxRuntimeExports.jsx("button", { className: "ps-tryon-sr-glass-btn", onClick: () => setShowLines(!showLines), children: showLines ? t2("Hide Fit") : t2("Show Fit") }),
21626
21475
  /* @__PURE__ */ jsxRuntimeExports.jsx("button", { className: "ps-tryon-sr-glass-btn", onClick: handleDownload, children: t2("Download") })
21627
- ] }),
21628
- /* @__PURE__ */ jsxRuntimeExports.jsx(RegenScanOverlay, { active: !!retryLoading && !!resultImageUrl && !tryOnProcessing })
21476
+ ] })
21629
21477
  ] }),
21630
21478
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-tryon-v2-panel", children: [
21631
21479
  mismatchNotice,
21632
- /* @__PURE__ */ jsxRuntimeExports.jsx(
21633
- SectionDetailView,
21634
- {
21635
- sectionName,
21636
- section: singleSection,
21637
- sectionResult: singleResult,
21638
- sectionFound: sizingResult?.found,
21639
- userMeasurements: singleUserMeasurements,
21640
- unitLbl,
21641
- chartUnit: resultUnit,
21642
- lengthEntry: null,
21643
- onBack: () => {
21644
- if (resultImageUrl) {
21645
- onResetTryOn?.();
21646
- } else {
21647
- setView("body-profile");
21480
+ activeSection === sectionName ? (
21481
+ /* DETAIL VIEW — full size table, same as multi-garment */
21482
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
21483
+ SectionDetailView,
21484
+ {
21485
+ sectionName,
21486
+ section: singleSection,
21487
+ sectionResult: singleResult,
21488
+ sectionFound: sizingResult?.found,
21489
+ userMeasurements: singleUserMeasurements,
21490
+ unitLbl,
21491
+ chartUnit: resultUnit,
21492
+ lengthEntry: null,
21493
+ onBack: () => setActiveSection(null),
21494
+ backLabel: t2("Back"),
21495
+ internationalSizes: sizingResult?.internationalSizes,
21496
+ onTryOn: resultImageUrl || vtoExcluded ? void 0 : handleSingleTryOn,
21497
+ continueLabel: resultImageUrl ? t2("Continue Shopping") : void 0,
21498
+ tryOnProcessing,
21499
+ tryOnStartedAt,
21500
+ t: t2,
21501
+ renderRaw: isAccessory
21502
+ }
21503
+ )
21504
+ ) : sizingResult?.found === false ? (
21505
+ /* NO-FIT EMPTY STATE — match% < 50% or backend reports
21506
+ no valid size. Skip the card + gallery + try-on
21507
+ entirely so the user gets immediate feedback that
21508
+ this product doesn't carry their size. */
21509
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-tryon-nofit", children: [
21510
+ /* @__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: [
21511
+ /* @__PURE__ */ jsxRuntimeExports.jsx("circle", { cx: "12", cy: "12", r: "10" }),
21512
+ /* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: "15", y1: "9", x2: "9", y2: "15" }),
21513
+ /* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: "9", y1: "9", x2: "15", y2: "15" })
21514
+ ] }) }),
21515
+ /* @__PURE__ */ jsxRuntimeExports.jsx("h3", { className: "ps-tryon-nofit-title", children: t2("No size matches your measurements") }),
21516
+ /* @__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.") }),
21517
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-tryon-nofit-actions", children: [
21518
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
21519
+ "button",
21520
+ {
21521
+ className: "ps-bp-back-btn",
21522
+ onClick: () => {
21523
+ if (resultImageUrl) onResetTryOn?.();
21524
+ else setView("body-profile");
21525
+ },
21526
+ type: "button",
21527
+ children: [
21528
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "ps-bp-back-arrow", children: "←" }),
21529
+ " ",
21530
+ t2("Back")
21531
+ ]
21532
+ }
21533
+ ),
21534
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("button", { className: "ps-tryon-v2-cta", style: { marginTop: 0 }, onClick: onClose, type: "button", children: [
21535
+ t2("Continue Shopping"),
21536
+ " →"
21537
+ ] })
21538
+ ] })
21539
+ ] })
21540
+ ) : (
21541
+ /* CARD VIEW — clickable summary card + gallery strip */
21542
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
21543
+ /* @__PURE__ */ jsxRuntimeExports.jsx("h3", { className: "ps-tryon-v2-title", children: t2("Your Perfect Fit") }),
21544
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "ps-tryon-v2-subtitle", children: t2("Tap the card for detailed breakdown") }),
21545
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-tryon-v2-sep" }),
21546
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-tryon-sr-cards-v2", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
21547
+ "button",
21548
+ {
21549
+ className: `ps-tryon-sr-card-v2 ps-full${pendingCustomSizes[sectionName] ? " ps-overridden" : ""}`,
21550
+ onClick: () => setActiveSection(sectionName),
21551
+ type: "button",
21552
+ children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-tryon-sr-card-v2-text", children: [
21553
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "ps-tryon-sr-card-v2-label", children: sectionName }),
21554
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "ps-tryon-sr-card-v2-value", children: pendingCustomSizes[sectionName]?.displayLabel || singleResult.recommendedSize || "—" }),
21555
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
21556
+ "span",
21557
+ {
21558
+ className: `ps-tryon-sr-card-v2-rec-pill${pendingCustomSizes[sectionName] ? " is-overridden" : ""}`,
21559
+ children: pendingCustomSizes[sectionName] ? t2("YOUR SELECTION") : t2("RECOMMENDED")
21560
+ }
21561
+ ),
21562
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "ps-tryon-sr-card-v2-view", children: [
21563
+ t2("VIEW DETAILS"),
21564
+ " ",
21565
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { "aria-hidden": "true", children: "›" })
21566
+ ] })
21567
+ ] })
21648
21568
  }
21649
- },
21650
- backLabel: t2("Back"),
21651
- internationalSizes: sizingResult?.internationalSizes,
21652
- onSelectForTryOn: setPendingCustomSize,
21653
- onRegenerateTryOn: isAccessory ? void 0 : resultImageUrl && onRegenerateTryOn ? onRegenerateTryOn : void 0,
21654
- retryLoading,
21655
- retryStartedAt,
21656
- pendingOverride: pendingCustomSizes[sectionName] ?? null,
21657
- onTryOn: resultImageUrl || vtoExcluded ? void 0 : handleSingleTryOn,
21658
- continueLabel: resultImageUrl ? t2("Continue Shopping") : void 0,
21659
- tryOnProcessing,
21660
- tryOnStartedAt,
21661
- t: t2,
21662
- renderRaw: isAccessory
21663
- }
21569
+ ) }),
21570
+ productImages && productImages.length >= 2 && /* @__PURE__ */ jsxRuntimeExports.jsx(ProductPhotoCarouselCard, { photos: productImages, productTitle, t: t2 }),
21571
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { style: { display: "flex", alignItems: "center", justifyContent: "space-between", marginTop: "0.5vw", gap: "0.5vw" }, children: [
21572
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
21573
+ "button",
21574
+ {
21575
+ className: "ps-bp-back-btn",
21576
+ onClick: () => {
21577
+ if (resultImageUrl) onResetTryOn?.();
21578
+ else setView("body-profile");
21579
+ },
21580
+ type: "button",
21581
+ children: [
21582
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "ps-bp-back-arrow", children: "←" }),
21583
+ " ",
21584
+ t2("Back")
21585
+ ]
21586
+ }
21587
+ ),
21588
+ resultImageUrl && !tryOnProcessing ? /* @__PURE__ */ jsxRuntimeExports.jsxs("button", { className: "ps-tryon-v2-cta", style: { marginTop: 0 }, onClick: onClose, children: [
21589
+ t2("Continue Shopping"),
21590
+ " →"
21591
+ ] }) : vtoExcluded ? /* @__PURE__ */ jsxRuntimeExports.jsxs("button", { className: "ps-tryon-v2-cta", style: { marginTop: 0 }, onClick: onClose, children: [
21592
+ t2("Continue Shopping"),
21593
+ " →"
21594
+ ] }) : /* @__PURE__ */ jsxRuntimeExports.jsxs(
21595
+ "button",
21596
+ {
21597
+ className: "ps-tryon-v2-cta",
21598
+ style: { marginTop: 0 },
21599
+ disabled: tryOnProcessing,
21600
+ onClick: handleSingleTryOn,
21601
+ type: "button",
21602
+ children: [
21603
+ /* @__PURE__ */ jsxRuntimeExports.jsx(CameraIcon$1, { size: 14 }),
21604
+ " ",
21605
+ tryOnProcessing ? t2("Processing...") : t2("Try It On")
21606
+ ]
21607
+ }
21608
+ )
21609
+ ] })
21610
+ ] })
21664
21611
  )
21665
- ] }, "panel-single")
21612
+ ] }, `panel-single-${activeSection ? "detail" : "card"}`)
21666
21613
  ] });
21667
21614
  })()
21668
21615
  ),
@@ -27703,6 +27650,19 @@ function detectMeasurementType(title) {
27703
27650
  if (/\b(sunglass|sunglasses|eyewear|eyeglasses|glasses|spectacles|optical|goggles|frames|aviator|wayfarer|lens)\b/.test(t2)) return "face";
27704
27651
  return "body";
27705
27652
  }
27653
+ function computeMatchScore(recData) {
27654
+ if (!recData) return null;
27655
+ const all = [];
27656
+ for (const m2 of recData.matchDetails ?? []) all.push(m2);
27657
+ if (recData.sections) {
27658
+ for (const sec of Object.values(recData.sections)) {
27659
+ for (const m2 of sec?.matchDetails ?? []) all.push(m2);
27660
+ }
27661
+ }
27662
+ if (all.length === 0) return null;
27663
+ const good = all.filter((r2) => r2.fit === "good" || r2.fit === "a-bit-tight" || r2.fit === "a-bit-loose").length;
27664
+ return Math.round(good / all.length * 100);
27665
+ }
27706
27666
  function measurementTypeToVtoCategory(type) {
27707
27667
  if (type === "face") return "sunglasses";
27708
27668
  if (type === "head") return "hat";
@@ -27711,6 +27671,7 @@ function measurementTypeToVtoCategory(type) {
27711
27671
  }
27712
27672
  function PrimeStyleTryonInner({
27713
27673
  productImage,
27674
+ productImages,
27714
27675
  productTitle = "Product",
27715
27676
  productId,
27716
27677
  productDescription,
@@ -27751,9 +27712,7 @@ function PrimeStyleTryonInner({
27751
27712
  const [resultImageUrl, setResultImageUrl] = reactExports.useState(null);
27752
27713
  const [errorMessage, setErrorMessage] = reactExports.useState(null);
27753
27714
  const [dragOver, setDragOver] = reactExports.useState(false);
27754
- const [retryLoading, setRetryLoading] = reactExports.useState(false);
27755
27715
  const [tryOnStartedAt, setTryOnStartedAt] = reactExports.useState(null);
27756
- const [retryStartedAt, setRetryStartedAt] = reactExports.useState(null);
27757
27716
  const [activeSection, setActiveSection] = reactExports.useState(null);
27758
27717
  const [pendingCustomSizes, setPendingCustomSizes] = reactExports.useState({});
27759
27718
  const pendingCustomSizesRef = reactExports.useRef({});
@@ -27763,7 +27722,7 @@ function PrimeStyleTryonInner({
27763
27722
  const sizingResultRef = reactExports.useRef(null);
27764
27723
  const sizeGuideRef = reactExports.useRef(null);
27765
27724
  const resultImageUrlRef = reactExports.useRef(null);
27766
- const setPendingCustomSizeBySection = reactExports.useCallback((sectionName, override) => {
27725
+ reactExports.useCallback((sectionName, override) => {
27767
27726
  setPendingCustomSizes((prev) => {
27768
27727
  const next = { ...prev };
27769
27728
  if (override === null) delete next[sectionName];
@@ -27785,6 +27744,7 @@ function PrimeStyleTryonInner({
27785
27744
  const [estimationDone, setEstimationDone] = reactExports.useState(false);
27786
27745
  const [tryOnProcessing, setTryOnProcessing] = reactExports.useState(false);
27787
27746
  const [sizeGuide, setSizeGuide] = reactExports.useState(null);
27747
+ const noFitFoundRef = reactExports.useRef(false);
27788
27748
  reactExports.useEffect(() => {
27789
27749
  sizingResultRef.current = sizingResult;
27790
27750
  }, [sizingResult]);
@@ -27843,6 +27803,7 @@ function PrimeStyleTryonInner({
27843
27803
  const [faceLandmarks, setFaceLandmarks] = reactExports.useState(null);
27844
27804
  const selectedFileRef = reactExports.useRef(null);
27845
27805
  const modelImageIdRef = reactExports.useRef(null);
27806
+ const autoTryOnFiredRef = reactExports.useRef(false);
27846
27807
  reactExports.useEffect(() => {
27847
27808
  try {
27848
27809
  const key = getApiKey();
@@ -28250,6 +28211,15 @@ function PrimeStyleTryonInner({
28250
28211
  unsubRef.current = null;
28251
28212
  }, []);
28252
28213
  const handleVtoUpdate = reactExports.useCallback((update) => {
28214
+ if (noFitFoundRef.current) {
28215
+ if (update.status === "completed" || update.status === "failed") {
28216
+ completedRef.current = true;
28217
+ cleanupJob();
28218
+ setTryOnProcessing(false);
28219
+ setTryOnStartedAt(null);
28220
+ }
28221
+ return;
28222
+ }
28253
28223
  if (update.status === "completed" && update.imageUrl) {
28254
28224
  setResultImageUrl((prev) => {
28255
28225
  if (!prev || prev.startsWith("data:")) return update.imageUrl;
@@ -28432,11 +28402,15 @@ function PrimeStyleTryonInner({
28432
28402
  }
28433
28403
  setEstimationDone(false);
28434
28404
  try {
28405
+ const tReq = Date.now();
28406
+ const payloadBytes = JSON.stringify(payload).length;
28407
+ console.log(`[ps-sdk:T] ▶ POST /sizing/recommend (quick) payload=${Math.round(payloadBytes / 1024)}KB bodyImage=${!!payload.bodyImage}`);
28435
28408
  const res = await fetch(`${baseUrl}/api/v1/sizing/recommend`, {
28436
28409
  method: "POST",
28437
28410
  headers: { "Content-Type": "application/json", Authorization: `Bearer ${key}` },
28438
28411
  body: JSON.stringify(payload)
28439
28412
  });
28413
+ console.log(`[ps-sdk:T] ◀ /sizing/recommend (quick) status=${res.status} roundTrip=${Date.now() - tReq}ms`);
28440
28414
  if (res.ok) {
28441
28415
  const data = await res.json();
28442
28416
  console.log("[PS-SDK] Sizing recommend RESULT:", JSON.stringify(data));
@@ -28572,6 +28546,8 @@ function PrimeStyleTryonInner({
28572
28546
  const objUrl = data.photoFile ? URL.createObjectURL(data.photoFile) : data.photoBase64.startsWith("data:") ? data.photoBase64 : `data:image/jpeg;base64,${data.photoBase64}`;
28573
28547
  setPreviewUrl(objUrl);
28574
28548
  completedRef.current = false;
28549
+ modelImageIdRef.current = null;
28550
+ noFitFoundRef.current = false;
28575
28551
  setTryOnProcessing(false);
28576
28552
  setTryOnStartedAt(null);
28577
28553
  setSizingResult(null);
@@ -28582,12 +28558,12 @@ function PrimeStyleTryonInner({
28582
28558
  const measurementType = detectMeasurementType(productTitle);
28583
28559
  if (measurementType === "face" || measurementType === "head") {
28584
28560
  setFaceLandmarks(null);
28585
- const minVisible = new Promise((r2) => setTimeout(r2, 4500));
28561
+ const minVisible2 = new Promise((r2) => setTimeout(r2, 4500));
28586
28562
  try {
28587
28563
  const faceResult = await detectFaceMeasurements(objUrl);
28588
28564
  if (!faceResult) {
28589
28565
  console.warn("[ps-sdk] face detection returned no result — likely a full-body photo for a face/head product");
28590
- await minVisible;
28566
+ await minVisible2;
28591
28567
  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.");
28592
28568
  setErrorMessage(msg);
28593
28569
  setView("error");
@@ -28615,7 +28591,7 @@ function PrimeStyleTryonInner({
28615
28591
  });
28616
28592
  if (recRes.ok) {
28617
28593
  const recData = await recRes.json();
28618
- await minVisible;
28594
+ await minVisible2;
28619
28595
  setSizingResult(recData);
28620
28596
  onComplete?.(recData);
28621
28597
  persistResultToProfile(
@@ -28632,12 +28608,12 @@ function PrimeStyleTryonInner({
28632
28608
  { skipBodyEstimate: true }
28633
28609
  );
28634
28610
  } else {
28635
- await minVisible;
28611
+ await minVisible2;
28636
28612
  setEstimationDone(true);
28637
28613
  }
28638
28614
  } catch (err) {
28639
28615
  console.error("[ps-sdk] face-recommend failed:", err);
28640
- await minVisible;
28616
+ await minVisible2;
28641
28617
  setEstimationDone(true);
28642
28618
  }
28643
28619
  setSizingLoading(false);
@@ -28707,12 +28683,17 @@ function PrimeStyleTryonInner({
28707
28683
  const jointCount = lmObj ? Object.keys(lmObj).filter((k2) => k2 !== "imageWidth" && k2 !== "imageHeight" && lmObj[k2]).length : 0;
28708
28684
  console.log(`[ps-sdk:debug] payload → bodyLandmarks=${!!lmObj}(${jointCount} joints)`);
28709
28685
  }
28686
+ const minVisible = new Promise((r2) => setTimeout(r2, 6e3));
28710
28687
  try {
28688
+ const tReq = Date.now();
28689
+ const payloadBytes = JSON.stringify(payload).length;
28690
+ console.log(`[ps-sdk:T] ▶ POST /sizing/recommend payload=${Math.round(payloadBytes / 1024)}KB bodyImage=${!!payload.bodyImage} landmarks=${!!payload.bodyLandmarks}`);
28711
28691
  const recRes = await fetch(`${baseUrl}/api/v1/sizing/recommend`, {
28712
28692
  method: "POST",
28713
28693
  headers: { "Content-Type": "application/json", Authorization: `Bearer ${key}` },
28714
28694
  body: JSON.stringify(payload)
28715
28695
  });
28696
+ console.log(`[ps-sdk:T] ◀ /sizing/recommend status=${recRes.status} roundTrip=${Date.now() - tReq}ms`);
28716
28697
  if (recRes.ok) {
28717
28698
  const recData = await recRes.json();
28718
28699
  if (recData?.found === false && recData?.reasoning === "NO_SIZE_CHART") {
@@ -28721,26 +28702,38 @@ function PrimeStyleTryonInner({
28721
28702
  setSizingLoading(false);
28722
28703
  return;
28723
28704
  }
28724
- setSizingResult(recData);
28725
- onComplete?.(recData);
28726
- persistResultToProfile(
28727
- {
28728
- gender: data.gender,
28729
- height: data.height,
28730
- weight: data.weight,
28731
- heightUnit: data.heightUnit,
28732
- weightUnit: data.weightUnit,
28733
- age: data.age,
28734
- bodyImage: data.photoBase64
28735
- },
28736
- recData
28737
- );
28705
+ const matchScore = computeMatchScore(recData);
28706
+ const isLowFit = recData?.found === false || matchScore != null && matchScore < 50;
28707
+ if (isLowFit) {
28708
+ console.log(`[ps-sdk:gate] LOW FIT (match=${matchScore ?? "?"}%, found=${recData?.found}) — suppressing try-on`);
28709
+ noFitFoundRef.current = true;
28710
+ setTryOnProcessing(false);
28711
+ setTryOnStartedAt(null);
28712
+ setSizingResult({ ...recData, found: false });
28713
+ onComplete?.({ ...recData, found: false });
28714
+ } else {
28715
+ setSizingResult(recData);
28716
+ onComplete?.(recData);
28717
+ persistResultToProfile(
28718
+ {
28719
+ gender: data.gender,
28720
+ height: data.height,
28721
+ weight: data.weight,
28722
+ heightUnit: data.heightUnit,
28723
+ weightUnit: data.weightUnit,
28724
+ age: data.age,
28725
+ bodyImage: data.photoBase64
28726
+ },
28727
+ recData
28728
+ );
28729
+ }
28738
28730
  } else {
28739
28731
  setEstimationDone(true);
28740
28732
  }
28741
28733
  } catch {
28742
28734
  setEstimationDone(true);
28743
28735
  }
28736
+ await minVisible;
28744
28737
  setSizingLoading(false);
28745
28738
  }, [apiUrl, productImage, productTitle, sizingUnit, weightUnit, sizingCountry, sizeGuide, dynamicFields, persistResultToProfile]);
28746
28739
  snapSubmitRef.current = handleSnapSubmit;
@@ -28764,9 +28757,9 @@ function PrimeStyleTryonInner({
28764
28757
  const isApparel = vtoCategory === "apparel";
28765
28758
  const previewObjUrl = (overrideFile ? null : previewUrl) || URL.createObjectURL(file);
28766
28759
  if (overrideFile || !previewUrl) setPreviewUrl(previewObjUrl);
28767
- modelPoseRef.current = null;
28768
- setBodyLandmarks(null);
28769
- if (isApparel) {
28760
+ if (isApparel && (!modelPoseRef.current || !bodyLandmarks)) {
28761
+ modelPoseRef.current = null;
28762
+ setBodyLandmarks(null);
28770
28763
  detectMeasurementLines(previewObjUrl).then((lines) => {
28771
28764
  modelPoseRef.current = lines;
28772
28765
  }).catch(() => {
@@ -28817,20 +28810,20 @@ function PrimeStyleTryonInner({
28817
28810
  console.log("[ps-sdk:flatten] sizingResult keys:", Object.keys(sizingResult || {}));
28818
28811
  console.log("[ps-sdk:flatten] root matchDetails:", (sizingResult?.matchDetails || []).map((m2) => m2.measurement));
28819
28812
  console.log("[ps-sdk:flatten] sections:", sizingResult?.sections ? Object.keys(sizingResult.sections) : "none");
28820
- const push = (md2, src) => {
28813
+ const push = (md2, src, section) => {
28821
28814
  if (!md2) return;
28822
28815
  console.log(`[ps-sdk:flatten] ${src} →`, md2.map((m2) => m2.measurement));
28823
28816
  for (const m2 of md2) {
28824
28817
  const k2 = m2.measurement.toLowerCase().replace(/\s*\(.*?\)\s*/g, "").trim();
28825
28818
  if (seen.has(k2)) continue;
28826
28819
  seen.add(k2);
28827
- out.push(m2);
28820
+ out.push({ ...m2, section });
28828
28821
  }
28829
28822
  };
28830
- push(sizingResult?.matchDetails, "root");
28823
+ push(sizingResult?.matchDetails, "root", void 0);
28831
28824
  if (sizingResult?.sections) {
28832
28825
  for (const [secName, sec] of Object.entries(sizingResult.sections)) {
28833
- push(sec?.matchDetails, `section:${secName}`);
28826
+ push(sec?.matchDetails, `section:${secName}`, secName);
28834
28827
  }
28835
28828
  }
28836
28829
  console.log("[ps-sdk:flatten] final unique:", out.map((m2) => m2.measurement));
@@ -28839,7 +28832,9 @@ function PrimeStyleTryonInner({
28839
28832
  const effectiveMatchDetails = override?.matchDetails && override.matchDetails.length ? override.matchDetails : flattenAllMatchDetails();
28840
28833
  let fitInfo;
28841
28834
  if (isApparel && effectiveMatchDetails.length) {
28842
- fitInfo = buildFitInfo(effectiveMatchDetails, modelPoseRef.current);
28835
+ const unitRaw = (sizingResult?.unit || sizingUnit || "in").toString().toLowerCase();
28836
+ const unit = unitRaw === "cm" ? "cm" : unitRaw === "mm" ? "mm" : "in";
28837
+ fitInfo = buildFitInfo(effectiveMatchDetails, modelPoseRef.current, unit);
28843
28838
  }
28844
28839
  console.log("[ps-sdk:tryon] fitInfo built", { count: fitInfo?.length || 0, areas: fitInfo?.map((f2) => `${f2.area}(${f2.fit})`) });
28845
28840
  const response = await apiRef.current.submitTryOn(
@@ -28888,167 +28883,19 @@ function PrimeStyleTryonInner({
28888
28883
  onError?.({ message, code });
28889
28884
  }
28890
28885
  }, [selectedFile, productImage, productTitle, sizingResult, onProcessing, onError, handleVtoUpdate]);
28891
- const handleRetryWithFit = reactExports.useCallback(async (fitInfo, selectedSizeOverride) => {
28892
- const file = selectedFile || selectedFileRef.current;
28893
- if (!file || !apiRef.current || !sseRef.current) {
28894
- console.warn("[ps-sdk:retry] skipping — no file/api/sse", {
28895
- hasFile: !!file,
28896
- hasApi: !!apiRef.current,
28897
- hasSse: !!sseRef.current
28898
- });
28899
- return;
28900
- }
28901
- console.log("[ps-sdk:retry] starting", { fitInfoCount: fitInfo.length, selectedSizeOverride, fitInfo });
28902
- setRetryLoading(true);
28903
- setRetryStartedAt(Date.now());
28904
- const vtoCategory = measurementTypeToVtoCategory(detectMeasurementType(productTitle));
28905
- const isApparel = vtoCategory === "apparel";
28906
- if (isApparel && modelPoseRef.current) {
28907
- const AREA_MAP = {
28908
- chest: "chest",
28909
- bust: "chest",
28910
- waist: "waist",
28911
- hips: "hips",
28912
- hip: "hips"
28913
- };
28914
- for (const area of fitInfo) {
28915
- const key = AREA_MAP[area.area.toLowerCase()];
28916
- if (key && modelPoseRef.current[key]) {
28917
- const line = modelPoseRef.current[key];
28918
- area.y = Math.round(line.y * 1e3) / 1e3;
28919
- area.x1 = Math.round(line.x1 * 1e3) / 1e3;
28920
- area.x2 = Math.round(line.x2 * 1e3) / 1e3;
28921
- }
28922
- }
28923
- }
28924
- try {
28925
- completedRef.current = false;
28926
- unsubRef.current?.();
28927
- if (pollingRef.current) {
28928
- clearInterval(pollingRef.current);
28929
- pollingRef.current = null;
28930
- }
28931
- const useExistingResult = false;
28932
- const modelImage = await compressImage(file, { maxDimension: 1024, quality: 0.85 });
28933
- console.log("[ps-sdk:retry] modelImage source", {
28934
- useExistingResult,
28935
- bytes: modelImage.length,
28936
- isPng: modelImage.startsWith("data:image/png")
28937
- });
28938
- const outboundFitInfo = isApparel ? fitInfo : void 0;
28939
- const response = await apiRef.current.submitTryOn(
28940
- modelImage,
28941
- productImage,
28942
- outboundFitInfo,
28943
- vtoCategory ?? "apparel",
28944
- {
28945
- editFromPrevious: useExistingResult,
28946
- productId: effectiveProductId,
28947
- productTitle,
28948
- productDescription,
28949
- productMaterial,
28950
- silhouetteContext: buildSilhouetteContext(sizingResultRef.current, sizeGuideRef.current, selectedSizeOverride),
28951
- modelImageId: modelImageIdRef.current ?? void 0
28952
- }
28953
- );
28954
- if (response.modelImageId) modelImageIdRef.current = response.modelImageId;
28955
- unsubRef.current = sseRef.current.onJob(response.jobId, (update) => {
28956
- if (update.status === "completed" && update.imageUrl) {
28957
- setResultImageUrl(update.imageUrl);
28958
- setRetryLoading(false);
28959
- setRetryStartedAt(null);
28960
- completedRef.current = true;
28961
- unsubRef.current?.();
28962
- unsubRef.current = null;
28963
- if (pollingRef.current) {
28964
- clearInterval(pollingRef.current);
28965
- pollingRef.current = null;
28966
- }
28967
- } else if (update.status === "failed") {
28968
- setRetryLoading(false);
28969
- setRetryStartedAt(null);
28970
- completedRef.current = true;
28971
- unsubRef.current?.();
28972
- unsubRef.current = null;
28973
- if (pollingRef.current) {
28974
- clearInterval(pollingRef.current);
28975
- pollingRef.current = null;
28976
- }
28977
- }
28978
- });
28979
- let attempts = 0;
28980
- pollingRef.current = setInterval(async () => {
28981
- if (completedRef.current) {
28982
- if (pollingRef.current) clearInterval(pollingRef.current);
28983
- pollingRef.current = null;
28984
- return;
28985
- }
28986
- attempts++;
28987
- if (attempts > 60) {
28988
- if (pollingRef.current) clearInterval(pollingRef.current);
28989
- pollingRef.current = null;
28990
- setRetryLoading(false);
28991
- setRetryStartedAt(null);
28992
- return;
28993
- }
28994
- try {
28995
- const status = await apiRef.current.getStatus(response.jobId);
28996
- if (status.status === "completed" && status.imageUrl) {
28997
- if (!completedRef.current) {
28998
- completedRef.current = true;
28999
- setResultImageUrl(status.imageUrl);
29000
- setRetryLoading(false);
29001
- setRetryStartedAt(null);
29002
- unsubRef.current?.();
29003
- unsubRef.current = null;
29004
- }
29005
- if (pollingRef.current) {
29006
- clearInterval(pollingRef.current);
29007
- pollingRef.current = null;
29008
- }
29009
- } else if (status.status === "failed") {
29010
- completedRef.current = true;
29011
- setRetryLoading(false);
29012
- setRetryStartedAt(null);
29013
- if (pollingRef.current) {
29014
- clearInterval(pollingRef.current);
29015
- pollingRef.current = null;
29016
- }
29017
- }
29018
- } catch {
29019
- }
29020
- }, 3e3);
29021
- } catch {
29022
- setRetryLoading(false);
29023
- setRetryStartedAt(null);
29024
- }
29025
- }, [selectedFile, productImage, productTitle]);
29026
- const handleRegenerateTryOn = reactExports.useCallback((override) => {
29027
- console.log("[ps-sdk:regen] fired", { sectionName: override.sectionName, override, hasFile: !!selectedFile, hasRefFile: !!selectedFileRef.current });
29028
- setPendingCustomSizeBySection(override.sectionName, override);
29029
- const map = { ...pendingCustomSizesRef.current, [override.sectionName]: override };
29030
- const seen = /* @__PURE__ */ new Set();
29031
- const merged = [];
29032
- for (const key of Object.keys(map)) {
29033
- for (const m2 of map[key].matchDetails || []) {
29034
- const k2 = m2.measurement.toLowerCase().trim();
29035
- if (seen.has(k2)) continue;
29036
- seen.add(k2);
29037
- merged.push(m2);
29038
- }
29039
- }
29040
- if (!merged.length) {
29041
- console.warn("[ps-sdk:regen] no matchDetails across any section — cannot rebuild fitInfo");
28886
+ reactExports.useEffect(() => {
28887
+ if (!sizingResult) {
28888
+ autoTryOnFiredRef.current = false;
29042
28889
  return;
29043
28890
  }
29044
- const fitInfo = buildFitInfo(merged, modelPoseRef.current);
29045
- const compositeLabel = Object.keys(map).map((s) => {
29046
- const cleanName = s.replace(/\s*[—–-]\s*.*/g, "").trim();
29047
- return `${cleanName} ${map[s].displayLabel}`;
29048
- }).join(", ");
29049
- console.log("[ps-sdk:regen] merged fitInfo built", { fitInfo, compositeLabel, sections: Object.keys(map) });
29050
- handleRetryWithFit(fitInfo, compositeLabel);
29051
- }, [handleRetryWithFit, selectedFile, setPendingCustomSizeBySection]);
28891
+ if (sizingResult.found === false) return;
28892
+ if (noFitFoundRef.current) return;
28893
+ if (autoTryOnFiredRef.current) return;
28894
+ if (tryOnProcessing || resultImageUrl) return;
28895
+ if (!selectedFile && !selectedFileRef.current) return;
28896
+ autoTryOnFiredRef.current = true;
28897
+ handleTryOnSubmit();
28898
+ }, [sizingResult, tryOnProcessing, resultImageUrl, selectedFile, handleTryOnSubmit]);
29052
28899
  const handleDownload = reactExports.useCallback(() => {
29053
28900
  if (!resultImageUrl) return;
29054
28901
  if (resultImageUrl.startsWith("data:")) {
@@ -29490,15 +29337,13 @@ function PrimeStyleTryonInner({
29490
29337
  sizeGuide,
29491
29338
  resultImageUrl,
29492
29339
  productImage,
29340
+ productImages,
29493
29341
  productTitle,
29494
29342
  productMaterial,
29495
29343
  productDescription,
29496
29344
  sizingUnit,
29497
29345
  setView,
29498
29346
  handleDownload,
29499
- onRetryWithFit: handleRetryWithFit,
29500
- retryLoading,
29501
- retryStartedAt,
29502
29347
  selectedFile,
29503
29348
  previewUrl,
29504
29349
  handleFileSelect,
@@ -29511,9 +29356,6 @@ function PrimeStyleTryonInner({
29511
29356
  measurementType: detectMeasurementType(productTitle),
29512
29357
  activeSection,
29513
29358
  setActiveSection,
29514
- pendingCustomSizes,
29515
- onPendingCustomSizeChange: setPendingCustomSizeBySection,
29516
- onRegenerateTryOn: handleRegenerateTryOn,
29517
29359
  onResetTryOn: () => {
29518
29360
  setSelectedFile(null);
29519
29361
  if (previewUrl) URL.revokeObjectURL(previewUrl);