@primestyleai/tryon 5.10.102 → 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;
11403
11442
  }
11404
- .ps-tryon-v2-processing-label > span:first-child {
11405
- animation: ps-loading-pulse 2s ease-in-out infinite;
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);
11558
+ }
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,133 +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 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
18918
  const SKELETON_CONNECTIONS$1 = [
18872
18919
  ["leftShoulder", "rightShoulder"],
18873
18920
  ["leftShoulder", "leftElbow"],
@@ -18953,30 +19000,6 @@ function MobileScanningView({
18953
19000
  onSwitchToManual,
18954
19001
  t: t2
18955
19002
  }) {
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
19003
  const displayImage = previewUrl || productImage || "";
18981
19004
  const isPhotoMode = !!previewUrl;
18982
19005
  const stages = isPhotoMode ? [
@@ -18996,11 +19019,18 @@ function MobileScanningView({
18996
19019
  const img = e.currentTarget;
18997
19020
  setDims({ w: img.naturalWidth || img.offsetWidth, h: img.naturalHeight || img.offsetHeight });
18998
19021
  };
19022
+ const TOTAL_MS = 6e3;
19023
+ const LAST_HOLD_MS = 1e3;
19024
+ const startRef = reactExports.useRef(Date.now());
18999
19025
  const [stageIdx, setStageIdx] = reactExports.useState(0);
19000
19026
  reactExports.useEffect(() => {
19027
+ const stepMs = (TOTAL_MS - LAST_HOLD_MS) / Math.max(1, stages.length - 1);
19001
19028
  const id2 = setInterval(() => {
19002
- setStageIdx((i) => (i + 1) % stages.length);
19003
- }, 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);
19004
19034
  return () => clearInterval(id2);
19005
19035
  }, [stages.length]);
19006
19036
  reactExports.useEffect(() => {
@@ -19235,6 +19265,132 @@ function MultiSectionMobile({
19235
19265
  sizeGuide ? null : null
19236
19266
  ] });
19237
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
+ }
19238
19394
  function garmentIconForSection(name) {
19239
19395
  const n2 = name.toLowerCase();
19240
19396
  if (n2.includes("jacket") || n2.includes("blazer") || n2.includes("coat")) return garmentJacketImg;
@@ -19258,62 +19414,6 @@ const SKELETON_CONNECTIONS = [
19258
19414
  ["rightHip", "rightKnee"],
19259
19415
  ["rightKnee", "rightAnkle"]
19260
19416
  ];
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
19417
  function FaceOverlay({
19318
19418
  landmarks,
19319
19419
  imgWidth,
@@ -19440,14 +19540,20 @@ function StageCycler({
19440
19540
  { title: t2("MATCHING SIZE"), desc: t2("Comparing your measurements to the size guide.") },
19441
19541
  { title: t2("FINALIZING RESULT"), desc: t2("Almost done — preparing your recommendation.") }
19442
19542
  ];
19543
+ const TOTAL_MS = 6e3;
19544
+ const LAST_HOLD_MS = 1e3;
19545
+ const startRef = reactExports.useRef(Date.now());
19443
19546
  const [idx, setIdx] = reactExports.useState(0);
19444
19547
  reactExports.useEffect(() => {
19445
- if (sizingDone) return;
19548
+ const stepMs = (TOTAL_MS - LAST_HOLD_MS) / Math.max(1, sizingStages.length - 1);
19446
19549
  const id2 = setInterval(() => {
19447
- setIdx((i) => Math.min(i + 1, sizingStages.length - 1));
19448
- }, 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);
19449
19555
  return () => clearInterval(id2);
19450
- }, [sizingDone, sizingStages.length]);
19556
+ }, [sizingStages.length]);
19451
19557
  const current = sizingStages[idx] ?? sizingStages[0];
19452
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: [
19453
19559
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-msc-stage-title", children: current.title }),
@@ -19721,19 +19827,11 @@ function SectionDetailView({
19721
19827
  internationalSizes,
19722
19828
  continueLabel,
19723
19829
  renderRaw = false,
19724
- sectionFound,
19725
- onSelectForTryOn,
19726
- onRegenerateTryOn,
19727
- pendingOverride,
19728
- retryLoading,
19729
- retryStartedAt
19830
+ sectionFound
19730
19831
  }) {
19731
19832
  const recSize = sectionResult?.recommendedSize || "";
19732
- const [selectedSize, setSelectedSize] = reactExports.useState(
19733
- pendingOverride?.selectedSize && pendingOverride.selectedSize !== (sectionResult?.recommendedSize || "") ? pendingOverride.selectedSize : null
19734
- );
19833
+ const [selectedSize, setSelectedSize] = reactExports.useState(null);
19735
19834
  const tryOnElapsedS = useElapsedSeconds(tryOnStartedAt ?? null);
19736
- const retryElapsedS = useElapsedSeconds(retryStartedAt ?? null);
19737
19835
  const unitLblLower = unitLbl.toLowerCase();
19738
19836
  const displayUnitId = unitLblLower.includes("mm") ? "mm" : unitLblLower.includes("cm") ? "cm" : "in";
19739
19837
  const fromUnit = chartUnit || displayUnitId;
@@ -19746,9 +19844,7 @@ function SectionDetailView({
19746
19844
  const countryOptions = internationalSizes ? Object.keys(internationalSizes) : [];
19747
19845
  const [selectedCountry, setSelectedCountry] = reactExports.useState(null);
19748
19846
  const recLength = lengthEntry?.secResult?.recommendedSize || "";
19749
- const [selectedLength, setSelectedLength] = reactExports.useState(
19750
- pendingOverride?.selectedLength && pendingOverride.selectedLength !== recLength ? pendingOverride.selectedLength : null
19751
- );
19847
+ const [selectedLength, setSelectedLength] = reactExports.useState(null);
19752
19848
  const lengthSizes = reactExports.useMemo(() => {
19753
19849
  if (!lengthEntry) return [];
19754
19850
  const sec = lengthEntry.section;
@@ -19975,8 +20071,8 @@ function SectionDetailView({
19975
20071
  const userInColUnit = colIsCm && userIsInches ? +(userNum2 * 2.54).toFixed(1) : !colIsCm && !userIsInches ? +(userNum2 * 2.54).toFixed(1) : userNum2;
19976
20072
  const range2 = rMaxRaw - rMinRaw;
19977
20073
  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";
20074
+ const tol = colIsCm ? 2.54 : 1;
20075
+ if (userInColUnit > rMinRaw - tol && userInColUnit < rMaxRaw + tol) fit2 = "good";
19980
20076
  else if (userInColUnit < rMinRaw) {
19981
20077
  const diff = rMinRaw - userInColUnit;
19982
20078
  fit2 = diff > threshold2 * 2 ? "too-long" : diff > threshold2 ? "long" : "a-bit-long";
@@ -20009,8 +20105,10 @@ function SectionDetailView({
20009
20105
  const measLower = m2.measurement.toLowerCase();
20010
20106
  const isDirectional = /length|inseam|sleeve|hem|rise/.test(measLower);
20011
20107
  let fit;
20012
- const tol = Math.max((rMax || rMin) * 0.03, 0.5);
20013
- 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) {
20014
20112
  fit = "good";
20015
20113
  } else if (isDirectional) {
20016
20114
  const diff = userNum > rMax ? userNum - rMax : rMin - userNum;
@@ -20057,65 +20155,6 @@ function SectionDetailView({
20057
20155
  const start = Math.max(0, Math.min(lengthOptions.length - 3, idx - 1));
20058
20156
  return lengthOptions.slice(start, start + 3);
20059
20157
  })();
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
20158
  if (isMobileProp) {
20120
20159
  const cleanSectionName = sectionName.replace(/\s*[—–-]\s*.*/g, "");
20121
20160
  const measurementDesc = (area) => {
@@ -20599,169 +20638,7 @@ function SectionDetailView({
20599
20638
  s
20600
20639
  );
20601
20640
  }) })
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
- })() })
20641
+ ] })
20765
20642
  ] }) }),
20766
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: [
20767
20644
  /* @__PURE__ */ jsxRuntimeExports.jsxs("button", { className: "ps-bp-back-btn", onClick: onBack, type: "button", style: { fontSize: "0.7vw" }, children: [
@@ -20837,15 +20714,13 @@ function SizeResultView({
20837
20714
  sizeGuide,
20838
20715
  resultImageUrl,
20839
20716
  productImage,
20717
+ productImages,
20840
20718
  productTitle,
20841
20719
  productMaterial,
20842
20720
  productDescription,
20843
20721
  sizingUnit,
20844
20722
  setView,
20845
20723
  handleDownload,
20846
- onRetryWithFit,
20847
- retryLoading,
20848
- retryStartedAt,
20849
20724
  selectedFile,
20850
20725
  previewUrl,
20851
20726
  handleFileSelect,
@@ -20864,7 +20739,6 @@ function SizeResultView({
20864
20739
  userHeightCm,
20865
20740
  pendingCustomSizes: pendingCustomSizesProp,
20866
20741
  onPendingCustomSizeChange,
20867
- onRegenerateTryOn,
20868
20742
  t: t2
20869
20743
  }) {
20870
20744
  const resultUnitRaw = (sizingResult?.unit || sizingUnit || "").toString().toLowerCase();
@@ -20970,9 +20844,6 @@ function SizeResultView({
20970
20844
  const [poseReady, setPoseReady] = reactExports.useState(false);
20971
20845
  const [imgDims, setImgDims] = reactExports.useState({ w: 800, h: 1200 });
20972
20846
  const pendingCustomSizes = pendingCustomSizesProp ?? {};
20973
- const setPendingCustomSize = (sectionName, override) => {
20974
- onPendingCustomSizeChange?.(sectionName, override);
20975
- };
20976
20847
  const handleImgLoad = reactExports.useCallback((e) => {
20977
20848
  const el2 = e.currentTarget;
20978
20849
  if (el2.naturalWidth && el2.naturalHeight) {
@@ -21141,7 +21012,7 @@ function SizeResultView({
21141
21012
  const prettyLength = lengthRec.replace(/\s+/g, " ").trim();
21142
21013
  return `${baseSize} / ${prettyLength}`;
21143
21014
  }, [lengthEntries, allSectionEntries]);
21144
- const heightLineLabel = reactExports.useMemo(() => {
21015
+ reactExports.useMemo(() => {
21145
21016
  const cm = userHeightCm || 0;
21146
21017
  if (!cm) return "";
21147
21018
  if (unitLbl === "in") {
@@ -21153,8 +21024,8 @@ function SizeResultView({
21153
21024
  return `${Math.round(cm)} cm`;
21154
21025
  }, [userHeightCm, unitLbl]);
21155
21026
  const hasPhoto = !!previewUrl;
21156
- const isSnapProcessing = hasPhoto && (tryOnProcessing || sizingLoading && !sizingResult);
21157
- const isSizingOnly = !hasPhoto && sizingLoading && !sizingResult;
21027
+ const isSnapProcessing = hasPhoto && sizingLoading;
21028
+ const isSizingOnly = !hasPhoto && sizingLoading;
21158
21029
  const sizingDone = !!sizingResult;
21159
21030
  const tryOnDone = !!resultImageUrl && !tryOnProcessing;
21160
21031
  const allDone = hasPhoto ? sizingDone && tryOnDone : sizingDone;
@@ -21221,20 +21092,12 @@ function SizeResultView({
21221
21092
  onLoad: handleImgLoad
21222
21093
  }
21223
21094
  ),
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 })
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 })
21225
21096
  ] }),
21226
21097
  (() => {
21227
21098
  const isFaceCategory = measurementType === "face" || measurementType === "head";
21228
21099
  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(
21100
+ return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-tryon-sr-right-col", style: { display: "flex", alignItems: "center", justifyContent: "center" }, children: /* @__PURE__ */ jsxRuntimeExports.jsx(
21238
21101
  StageCycler,
21239
21102
  {
21240
21103
  category: isFaceCategory ? measurementType : "body",
@@ -21272,11 +21135,6 @@ function SizeResultView({
21272
21135
  })(),
21273
21136
  onBack: () => setActiveSection(null),
21274
21137
  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
21138
  productImage: resultImageUrl || productImage,
21281
21139
  productTitle,
21282
21140
  isMobile: true,
@@ -21314,10 +21172,7 @@ function SizeResultView({
21314
21172
  ] });
21315
21173
  }
21316
21174
  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
- ] }),
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" }) }),
21321
21176
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-tryon-v2-panel", children: [
21322
21177
  mismatchNotice,
21323
21178
  /* @__PURE__ */ jsxRuntimeExports.jsx(
@@ -21339,11 +21194,6 @@ function SizeResultView({
21339
21194
  })(),
21340
21195
  onBack: () => setActiveSection(null),
21341
21196
  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
21197
  t: t2
21348
21198
  }
21349
21199
  )
@@ -21413,11 +21263,8 @@ function SizeResultView({
21413
21263
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-tryon-v2", children: [
21414
21264
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-tryon-v2-bg", style: { position: "relative" }, children: [
21415
21265
  /* @__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 }),
21266
+ tryOnProcessing && /* @__PURE__ */ jsxRuntimeExports.jsx(TryOnGenerationBadge, { tryOnStartedAt: tryOnStartedAt ?? null, t: t2 }),
21267
+ /* @__PURE__ */ jsxRuntimeExports.jsx(RegenScanOverlay, { active: !!tryOnProcessing }),
21421
21268
  resultImageUrl && !tryOnProcessing && poseReady && poseLines && /* @__PURE__ */ jsxRuntimeExports.jsx(MeasurementOverlay, { lines: poseLines, fitRows: (() => {
21422
21269
  const all = [...sizingResult?.matchDetails || []];
21423
21270
  if (sizingResult?.sections) {
@@ -21483,11 +21330,15 @@ function SizeResultView({
21483
21330
  /* @__PURE__ */ jsxRuntimeExports.jsx(
21484
21331
  "span",
21485
21332
  {
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")
21333
+ className: `ps-tryon-sr-card-v2-rec-pill${isOverridden ? " is-overridden" : ""}`,
21334
+ children: isOverridden ? t2("YOUR SELECTION") : t2("RECOMMENDED")
21489
21335
  }
21490
- )
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
+ ] })
21491
21342
  ] }),
21492
21343
  sectionImg && /* @__PURE__ */ jsxRuntimeExports.jsx("img", { src: sectionImg, alt: name, className: "ps-tryon-sr-card-v2-img" })
21493
21344
  ] }, name);
@@ -21578,11 +21429,6 @@ function SizeResultView({
21578
21429
  },
21579
21430
  backLabel: t2("Back"),
21580
21431
  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
21432
  onTryOn: resultImageUrl || vtoExcluded ? void 0 : handleSingleTryOn,
21587
21433
  continueLabel: resultImageUrl ? t2("Continue Shopping") : void 0,
21588
21434
  tryOnProcessing,
@@ -21612,50 +21458,158 @@ function SizeResultView({
21612
21458
  }
21613
21459
  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-tryon-v2", children: [
21614
21460
  /* @__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 }),
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 }),
21617
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: [
21618
21474
  !isAccessory && /* @__PURE__ */ jsxRuntimeExports.jsx("button", { className: "ps-tryon-sr-glass-btn", onClick: () => setShowLines(!showLines), children: showLines ? t2("Hide Fit") : t2("Show Fit") }),
21619
21475
  /* @__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 })
21476
+ ] })
21622
21477
  ] }),
21623
21478
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-tryon-v2-panel", children: [
21624
21479
  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");
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
+ ] })
21641
21568
  }
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
- }
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
+ ] })
21657
21611
  )
21658
- ] }, "panel-single")
21612
+ ] }, `panel-single-${activeSection ? "detail" : "card"}`)
21659
21613
  ] });
21660
21614
  })()
21661
21615
  ),
@@ -27696,6 +27650,19 @@ function detectMeasurementType(title) {
27696
27650
  if (/\b(sunglass|sunglasses|eyewear|eyeglasses|glasses|spectacles|optical|goggles|frames|aviator|wayfarer|lens)\b/.test(t2)) return "face";
27697
27651
  return "body";
27698
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
+ }
27699
27666
  function measurementTypeToVtoCategory(type) {
27700
27667
  if (type === "face") return "sunglasses";
27701
27668
  if (type === "head") return "hat";
@@ -27704,6 +27671,7 @@ function measurementTypeToVtoCategory(type) {
27704
27671
  }
27705
27672
  function PrimeStyleTryonInner({
27706
27673
  productImage,
27674
+ productImages,
27707
27675
  productTitle = "Product",
27708
27676
  productId,
27709
27677
  productDescription,
@@ -27744,9 +27712,7 @@ function PrimeStyleTryonInner({
27744
27712
  const [resultImageUrl, setResultImageUrl] = reactExports.useState(null);
27745
27713
  const [errorMessage, setErrorMessage] = reactExports.useState(null);
27746
27714
  const [dragOver, setDragOver] = reactExports.useState(false);
27747
- const [retryLoading, setRetryLoading] = reactExports.useState(false);
27748
27715
  const [tryOnStartedAt, setTryOnStartedAt] = reactExports.useState(null);
27749
- const [retryStartedAt, setRetryStartedAt] = reactExports.useState(null);
27750
27716
  const [activeSection, setActiveSection] = reactExports.useState(null);
27751
27717
  const [pendingCustomSizes, setPendingCustomSizes] = reactExports.useState({});
27752
27718
  const pendingCustomSizesRef = reactExports.useRef({});
@@ -27756,7 +27722,7 @@ function PrimeStyleTryonInner({
27756
27722
  const sizingResultRef = reactExports.useRef(null);
27757
27723
  const sizeGuideRef = reactExports.useRef(null);
27758
27724
  const resultImageUrlRef = reactExports.useRef(null);
27759
- const setPendingCustomSizeBySection = reactExports.useCallback((sectionName, override) => {
27725
+ reactExports.useCallback((sectionName, override) => {
27760
27726
  setPendingCustomSizes((prev) => {
27761
27727
  const next = { ...prev };
27762
27728
  if (override === null) delete next[sectionName];
@@ -27778,6 +27744,7 @@ function PrimeStyleTryonInner({
27778
27744
  const [estimationDone, setEstimationDone] = reactExports.useState(false);
27779
27745
  const [tryOnProcessing, setTryOnProcessing] = reactExports.useState(false);
27780
27746
  const [sizeGuide, setSizeGuide] = reactExports.useState(null);
27747
+ const noFitFoundRef = reactExports.useRef(false);
27781
27748
  reactExports.useEffect(() => {
27782
27749
  sizingResultRef.current = sizingResult;
27783
27750
  }, [sizingResult]);
@@ -27836,6 +27803,7 @@ function PrimeStyleTryonInner({
27836
27803
  const [faceLandmarks, setFaceLandmarks] = reactExports.useState(null);
27837
27804
  const selectedFileRef = reactExports.useRef(null);
27838
27805
  const modelImageIdRef = reactExports.useRef(null);
27806
+ const autoTryOnFiredRef = reactExports.useRef(false);
27839
27807
  reactExports.useEffect(() => {
27840
27808
  try {
27841
27809
  const key = getApiKey();
@@ -28243,6 +28211,15 @@ function PrimeStyleTryonInner({
28243
28211
  unsubRef.current = null;
28244
28212
  }, []);
28245
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
+ }
28246
28223
  if (update.status === "completed" && update.imageUrl) {
28247
28224
  setResultImageUrl((prev) => {
28248
28225
  if (!prev || prev.startsWith("data:")) return update.imageUrl;
@@ -28425,11 +28402,15 @@ function PrimeStyleTryonInner({
28425
28402
  }
28426
28403
  setEstimationDone(false);
28427
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}`);
28428
28408
  const res = await fetch(`${baseUrl}/api/v1/sizing/recommend`, {
28429
28409
  method: "POST",
28430
28410
  headers: { "Content-Type": "application/json", Authorization: `Bearer ${key}` },
28431
28411
  body: JSON.stringify(payload)
28432
28412
  });
28413
+ console.log(`[ps-sdk:T] ◀ /sizing/recommend (quick) status=${res.status} roundTrip=${Date.now() - tReq}ms`);
28433
28414
  if (res.ok) {
28434
28415
  const data = await res.json();
28435
28416
  console.log("[PS-SDK] Sizing recommend RESULT:", JSON.stringify(data));
@@ -28565,6 +28546,8 @@ function PrimeStyleTryonInner({
28565
28546
  const objUrl = data.photoFile ? URL.createObjectURL(data.photoFile) : data.photoBase64.startsWith("data:") ? data.photoBase64 : `data:image/jpeg;base64,${data.photoBase64}`;
28566
28547
  setPreviewUrl(objUrl);
28567
28548
  completedRef.current = false;
28549
+ modelImageIdRef.current = null;
28550
+ noFitFoundRef.current = false;
28568
28551
  setTryOnProcessing(false);
28569
28552
  setTryOnStartedAt(null);
28570
28553
  setSizingResult(null);
@@ -28575,12 +28558,12 @@ function PrimeStyleTryonInner({
28575
28558
  const measurementType = detectMeasurementType(productTitle);
28576
28559
  if (measurementType === "face" || measurementType === "head") {
28577
28560
  setFaceLandmarks(null);
28578
- const minVisible = new Promise((r2) => setTimeout(r2, 4500));
28561
+ const minVisible2 = new Promise((r2) => setTimeout(r2, 4500));
28579
28562
  try {
28580
28563
  const faceResult = await detectFaceMeasurements(objUrl);
28581
28564
  if (!faceResult) {
28582
28565
  console.warn("[ps-sdk] face detection returned no result — likely a full-body photo for a face/head product");
28583
- await minVisible;
28566
+ await minVisible2;
28584
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.");
28585
28568
  setErrorMessage(msg);
28586
28569
  setView("error");
@@ -28608,7 +28591,7 @@ function PrimeStyleTryonInner({
28608
28591
  });
28609
28592
  if (recRes.ok) {
28610
28593
  const recData = await recRes.json();
28611
- await minVisible;
28594
+ await minVisible2;
28612
28595
  setSizingResult(recData);
28613
28596
  onComplete?.(recData);
28614
28597
  persistResultToProfile(
@@ -28625,12 +28608,12 @@ function PrimeStyleTryonInner({
28625
28608
  { skipBodyEstimate: true }
28626
28609
  );
28627
28610
  } else {
28628
- await minVisible;
28611
+ await minVisible2;
28629
28612
  setEstimationDone(true);
28630
28613
  }
28631
28614
  } catch (err) {
28632
28615
  console.error("[ps-sdk] face-recommend failed:", err);
28633
- await minVisible;
28616
+ await minVisible2;
28634
28617
  setEstimationDone(true);
28635
28618
  }
28636
28619
  setSizingLoading(false);
@@ -28700,12 +28683,17 @@ function PrimeStyleTryonInner({
28700
28683
  const jointCount = lmObj ? Object.keys(lmObj).filter((k2) => k2 !== "imageWidth" && k2 !== "imageHeight" && lmObj[k2]).length : 0;
28701
28684
  console.log(`[ps-sdk:debug] payload → bodyLandmarks=${!!lmObj}(${jointCount} joints)`);
28702
28685
  }
28686
+ const minVisible = new Promise((r2) => setTimeout(r2, 6e3));
28703
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}`);
28704
28691
  const recRes = await fetch(`${baseUrl}/api/v1/sizing/recommend`, {
28705
28692
  method: "POST",
28706
28693
  headers: { "Content-Type": "application/json", Authorization: `Bearer ${key}` },
28707
28694
  body: JSON.stringify(payload)
28708
28695
  });
28696
+ console.log(`[ps-sdk:T] ◀ /sizing/recommend status=${recRes.status} roundTrip=${Date.now() - tReq}ms`);
28709
28697
  if (recRes.ok) {
28710
28698
  const recData = await recRes.json();
28711
28699
  if (recData?.found === false && recData?.reasoning === "NO_SIZE_CHART") {
@@ -28714,26 +28702,38 @@ function PrimeStyleTryonInner({
28714
28702
  setSizingLoading(false);
28715
28703
  return;
28716
28704
  }
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
- );
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
+ }
28731
28730
  } else {
28732
28731
  setEstimationDone(true);
28733
28732
  }
28734
28733
  } catch {
28735
28734
  setEstimationDone(true);
28736
28735
  }
28736
+ await minVisible;
28737
28737
  setSizingLoading(false);
28738
28738
  }, [apiUrl, productImage, productTitle, sizingUnit, weightUnit, sizingCountry, sizeGuide, dynamicFields, persistResultToProfile]);
28739
28739
  snapSubmitRef.current = handleSnapSubmit;
@@ -28757,9 +28757,9 @@ function PrimeStyleTryonInner({
28757
28757
  const isApparel = vtoCategory === "apparel";
28758
28758
  const previewObjUrl = (overrideFile ? null : previewUrl) || URL.createObjectURL(file);
28759
28759
  if (overrideFile || !previewUrl) setPreviewUrl(previewObjUrl);
28760
- modelPoseRef.current = null;
28761
- setBodyLandmarks(null);
28762
- if (isApparel) {
28760
+ if (isApparel && (!modelPoseRef.current || !bodyLandmarks)) {
28761
+ modelPoseRef.current = null;
28762
+ setBodyLandmarks(null);
28763
28763
  detectMeasurementLines(previewObjUrl).then((lines) => {
28764
28764
  modelPoseRef.current = lines;
28765
28765
  }).catch(() => {
@@ -28810,20 +28810,20 @@ function PrimeStyleTryonInner({
28810
28810
  console.log("[ps-sdk:flatten] sizingResult keys:", Object.keys(sizingResult || {}));
28811
28811
  console.log("[ps-sdk:flatten] root matchDetails:", (sizingResult?.matchDetails || []).map((m2) => m2.measurement));
28812
28812
  console.log("[ps-sdk:flatten] sections:", sizingResult?.sections ? Object.keys(sizingResult.sections) : "none");
28813
- const push = (md2, src) => {
28813
+ const push = (md2, src, section) => {
28814
28814
  if (!md2) return;
28815
28815
  console.log(`[ps-sdk:flatten] ${src} →`, md2.map((m2) => m2.measurement));
28816
28816
  for (const m2 of md2) {
28817
28817
  const k2 = m2.measurement.toLowerCase().replace(/\s*\(.*?\)\s*/g, "").trim();
28818
28818
  if (seen.has(k2)) continue;
28819
28819
  seen.add(k2);
28820
- out.push(m2);
28820
+ out.push({ ...m2, section });
28821
28821
  }
28822
28822
  };
28823
- push(sizingResult?.matchDetails, "root");
28823
+ push(sizingResult?.matchDetails, "root", void 0);
28824
28824
  if (sizingResult?.sections) {
28825
28825
  for (const [secName, sec] of Object.entries(sizingResult.sections)) {
28826
- push(sec?.matchDetails, `section:${secName}`);
28826
+ push(sec?.matchDetails, `section:${secName}`, secName);
28827
28827
  }
28828
28828
  }
28829
28829
  console.log("[ps-sdk:flatten] final unique:", out.map((m2) => m2.measurement));
@@ -28832,7 +28832,9 @@ function PrimeStyleTryonInner({
28832
28832
  const effectiveMatchDetails = override?.matchDetails && override.matchDetails.length ? override.matchDetails : flattenAllMatchDetails();
28833
28833
  let fitInfo;
28834
28834
  if (isApparel && effectiveMatchDetails.length) {
28835
- 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);
28836
28838
  }
28837
28839
  console.log("[ps-sdk:tryon] fitInfo built", { count: fitInfo?.length || 0, areas: fitInfo?.map((f2) => `${f2.area}(${f2.fit})`) });
28838
28840
  const response = await apiRef.current.submitTryOn(
@@ -28881,167 +28883,19 @@ function PrimeStyleTryonInner({
28881
28883
  onError?.({ message, code });
28882
28884
  }
28883
28885
  }, [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");
28886
+ reactExports.useEffect(() => {
28887
+ if (!sizingResult) {
28888
+ autoTryOnFiredRef.current = false;
29035
28889
  return;
29036
28890
  }
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]);
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]);
29045
28899
  const handleDownload = reactExports.useCallback(() => {
29046
28900
  if (!resultImageUrl) return;
29047
28901
  if (resultImageUrl.startsWith("data:")) {
@@ -29483,15 +29337,13 @@ function PrimeStyleTryonInner({
29483
29337
  sizeGuide,
29484
29338
  resultImageUrl,
29485
29339
  productImage,
29340
+ productImages,
29486
29341
  productTitle,
29487
29342
  productMaterial,
29488
29343
  productDescription,
29489
29344
  sizingUnit,
29490
29345
  setView,
29491
29346
  handleDownload,
29492
- onRetryWithFit: handleRetryWithFit,
29493
- retryLoading,
29494
- retryStartedAt,
29495
29347
  selectedFile,
29496
29348
  previewUrl,
29497
29349
  handleFileSelect,
@@ -29504,9 +29356,6 @@ function PrimeStyleTryonInner({
29504
29356
  measurementType: detectMeasurementType(productTitle),
29505
29357
  activeSection,
29506
29358
  setActiveSection,
29507
- pendingCustomSizes,
29508
- onPendingCustomSizeChange: setPendingCustomSizeBySection,
29509
- onRegenerateTryOn: handleRegenerateTryOn,
29510
29359
  onResetTryOn: () => {
29511
29360
  setSelectedFile(null);
29512
29361
  if (previewUrl) URL.revokeObjectURL(previewUrl);