@primestyleai/tryon 5.9.1 → 5.10.0

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.
@@ -4,3 +4,9 @@ export declare function kgToLbs(kg: number): number;
4
4
  export declare function lbsToKg(lbs: number): number;
5
5
  export declare function ftInToCm(ft: number, inch: number): number;
6
6
  export declare function isImperial(locale: string): boolean;
7
+ /**
8
+ * Human-readable label for a unit token. Used to append a system label to
9
+ * primary CTAs ("Find My Best Fit (Metric)") so the user knows whether their
10
+ * values will be interpreted as cm/kg or in/lbs before they submit.
11
+ */
12
+ export declare function getUnitLabel(unit: string | undefined | null): string;
@@ -10080,7 +10080,8 @@ async function recommendForProduct(input) {
10080
10080
  sections: sectionsMap,
10081
10081
  profileId: profile.id,
10082
10082
  fromCache: false,
10083
- raw: result
10083
+ raw: result,
10084
+ found: result.found
10084
10085
  };
10085
10086
  }
10086
10087
  async function estimateFullMeasurements(args) {
@@ -10139,6 +10140,12 @@ async function estimateFullMeasurements(args) {
10139
10140
  function isImperial(locale) {
10140
10141
  return ["US", "UK", "AU"].includes(locale);
10141
10142
  }
10143
+ function getUnitLabel(unit) {
10144
+ if (unit === "in" || unit === "inches" || unit === "lbs") return "Imperial";
10145
+ if (unit === "cm" || unit === "kg") return "Metric";
10146
+ if (unit === "mm") return "mm";
10147
+ return "";
10148
+ }
10142
10149
  function cx(base, override) {
10143
10150
  return override ? `${base} ${override}` : base;
10144
10151
  }
@@ -11524,21 +11531,21 @@ const STYLES$1 = `
11524
11531
  /* Shared progress layout used inside StageCycler (desktop) and
11525
11532
  MobileScanningView — row of ring + bar + percent, same tokens. */
11526
11533
  .ps-tryon-progress-wrap {
11527
- display: flex; align-items: center; gap: 10px;
11528
- width: 100%; max-width: 320px; margin-top: 16px;
11534
+ display: flex; align-items: center; gap: 12px;
11535
+ width: 100%; max-width: 360px; margin-top: 18px;
11529
11536
  }
11530
11537
  .ps-tryon-progress-wrap .ps-tryon-progress-bar-wrap {
11531
- flex: 1; height: 4px; border-radius: 3px; overflow: hidden;
11538
+ flex: 1; height: 6px; border-radius: 4px; overflow: hidden;
11532
11539
  position: relative; background: var(--ps-border-color);
11533
11540
  }
11534
11541
  .ps-tryon-progress-wrap .ps-tryon-progress-bar-fill {
11535
11542
  height: 100%; width: 0%;
11536
11543
  background: linear-gradient(90deg, var(--ps-accent), var(--ps-accent-light));
11537
- border-radius: 3px; transition: width 0.3s ease;
11544
+ border-radius: 4px; transition: width 0.3s ease;
11538
11545
  }
11539
11546
  .ps-tryon-progress-wrap .ps-tryon-progress-pct {
11540
- font-size: 11px; font-weight: 700; color: var(--ps-accent);
11541
- min-width: 30px; text-align: right;
11547
+ font-size: 13px; font-weight: 700; color: var(--ps-accent);
11548
+ min-width: 36px; text-align: right;
11542
11549
  font-variant-numeric: tabular-nums;
11543
11550
  }
11544
11551
  .ps-tryon-progress-bar-wrap {
@@ -11566,25 +11573,25 @@ const STYLES$1 = `
11566
11573
  font-variant-numeric: tabular-nums;
11567
11574
  }
11568
11575
 
11569
- /* Circular ETA ring — 48×48 px SVG with a track + progress circle; ETA
11576
+ /* Circular ETA ring — 64×64 px SVG with a track + progress circle; ETA
11570
11577
  text centered. strokeDashoffset is driven by the ticker in
11571
11578
  PrimeStyleTryonInner, so CSS only styles the appearance. */
11572
11579
  .ps-tryon-progress-ring {
11573
- position: relative; width: 48px; height: 48px; flex: 0 0 48px;
11580
+ position: relative; width: 64px; height: 64px; flex: 0 0 64px;
11574
11581
  display: flex; align-items: center; justify-content: center;
11575
11582
  }
11576
11583
  .ps-tryon-progress-ring svg { transform: rotate(-90deg); }
11577
11584
  .ps-tryon-progress-ring-track {
11578
- fill: none; stroke: var(--ps-border-color); stroke-width: 3.5;
11585
+ fill: none; stroke: var(--ps-border-color); stroke-width: 5;
11579
11586
  }
11580
11587
  .ps-tryon-progress-ring-fill {
11581
- fill: none; stroke: var(--ps-accent); stroke-width: 3.5;
11588
+ fill: none; stroke: var(--ps-accent); stroke-width: 5;
11582
11589
  stroke-linecap: round;
11583
11590
  transition: stroke-dashoffset 0.3s ease;
11584
11591
  }
11585
11592
  .ps-tryon-progress-eta {
11586
11593
  position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
11587
- font-size: 10px; font-weight: 700; color: var(--ps-accent);
11594
+ font-size: 13px; font-weight: 700; color: var(--ps-accent);
11588
11595
  font-variant-numeric: tabular-nums; letter-spacing: 0.01em;
11589
11596
  pointer-events: none;
11590
11597
  }
@@ -16671,6 +16678,92 @@ function ProfileDetailModal({
16671
16678
  document.body
16672
16679
  );
16673
16680
  }
16681
+ function ConfirmMeasurementsModal({
16682
+ profile,
16683
+ onProceed,
16684
+ onEdit,
16685
+ t: t2
16686
+ }) {
16687
+ const heightUnit = profile.heightUnit === "in" || profile.heightUnit === "ft" ? "in" : "cm";
16688
+ const weightUnit = profile.weightUnit === "lbs" ? "lbs" : "kg";
16689
+ const systemLabel = getUnitLabel(heightUnit);
16690
+ const formatHeight = (h) => {
16691
+ if (!h) return "—";
16692
+ if (heightUnit === "in") {
16693
+ const ft = Math.floor(h / 12);
16694
+ const inches = Math.round(h % 12);
16695
+ return `${ft}'${inches}"`;
16696
+ }
16697
+ return `${Math.round(h)} cm`;
16698
+ };
16699
+ const formatWeight = (w2) => {
16700
+ if (!w2) return "—";
16701
+ return `${Math.round(w2)} ${weightUnit}`;
16702
+ };
16703
+ const height = profile.height ?? profile.heightCm;
16704
+ const weight = profile.weight ?? profile.weightKg;
16705
+ return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-confirm-overlay", onClick: onEdit, children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-confirm-modal", onClick: (e) => e.stopPropagation(), children: [
16706
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
16707
+ "button",
16708
+ {
16709
+ type: "button",
16710
+ "aria-label": t2("Close"),
16711
+ onClick: onEdit,
16712
+ style: {
16713
+ position: "absolute",
16714
+ top: "0.75vw",
16715
+ right: "0.75vw",
16716
+ width: "1.8vw",
16717
+ height: "1.8vw",
16718
+ borderRadius: "50%",
16719
+ background: "transparent",
16720
+ border: "none",
16721
+ cursor: "pointer",
16722
+ display: "flex",
16723
+ alignItems: "center",
16724
+ justifyContent: "center",
16725
+ color: "var(--ps-text-muted)"
16726
+ },
16727
+ children: /* @__PURE__ */ jsxRuntimeExports.jsxs("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", width: "16", height: "16", children: [
16728
+ /* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
16729
+ /* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: "6", y1: "6", x2: "18", y2: "18" })
16730
+ ] })
16731
+ }
16732
+ ),
16733
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { style: { fontWeight: 700, marginBottom: "0.4vw" }, children: t2("Confirm your measurements") }),
16734
+ /* @__PURE__ */ jsxRuntimeExports.jsx("small", { style: { color: "var(--ps-text-muted)" }, children: systemLabel ? t2("You chose") + " " + systemLabel + ". " + t2("Review before continuing.") : t2("Review before continuing.") }),
16735
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("ul", { style: {
16736
+ listStyle: "none",
16737
+ padding: 0,
16738
+ margin: "0.8vw 0 0.2vw 0",
16739
+ width: "100%",
16740
+ textAlign: "left",
16741
+ fontSize: "0.78vw",
16742
+ lineHeight: 1.6
16743
+ }, children: [
16744
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("li", { style: { display: "flex", justifyContent: "space-between", gap: "1vw" }, children: [
16745
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { style: { color: "var(--ps-text-muted)" }, children: t2("Height") }),
16746
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { style: { fontWeight: 600 }, children: formatHeight(height) })
16747
+ ] }),
16748
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("li", { style: { display: "flex", justifyContent: "space-between", gap: "1vw" }, children: [
16749
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { style: { color: "var(--ps-text-muted)" }, children: t2("Weight") }),
16750
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { style: { fontWeight: 600 }, children: formatWeight(weight) })
16751
+ ] }),
16752
+ profile.age ? /* @__PURE__ */ jsxRuntimeExports.jsxs("li", { style: { display: "flex", justifyContent: "space-between", gap: "1vw" }, children: [
16753
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { style: { color: "var(--ps-text-muted)" }, children: t2("Age") }),
16754
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { style: { fontWeight: 600 }, children: profile.age })
16755
+ ] }) : null,
16756
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("li", { style: { display: "flex", justifyContent: "space-between", gap: "1vw" }, children: [
16757
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { style: { color: "var(--ps-text-muted)" }, children: t2("Gender") }),
16758
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { style: { fontWeight: 600 }, children: profile.gender === "female" ? t2("Female") : t2("Male") })
16759
+ ] })
16760
+ ] }),
16761
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-confirm-actions", children: [
16762
+ /* @__PURE__ */ jsxRuntimeExports.jsx("button", { type: "button", className: "ps-confirm-cancel", onClick: onEdit, children: t2("Edit") }),
16763
+ /* @__PURE__ */ jsxRuntimeExports.jsx("button", { type: "button", className: "ps-confirm-delete", onClick: onProceed, children: t2("Proceed") })
16764
+ ] })
16765
+ ] }) });
16766
+ }
16674
16767
  function WelcomeView({
16675
16768
  productImage,
16676
16769
  setView,
@@ -16907,7 +17000,7 @@ function MobileSkeleton({ landmarks, w: w2, h }) {
16907
17000
  );
16908
17001
  }
16909
17002
  const MSC_TRYON_TARGET_SECONDS = 22;
16910
- const MSC_RING_RADIUS = 20;
17003
+ const MSC_RING_RADIUS = 27;
16911
17004
  const MSC_RING_CIRC = 2 * Math.PI * MSC_RING_RADIUS;
16912
17005
  function MscTryOnProgress({ t: t2 }) {
16913
17006
  const startRef = reactExports.useRef(Date.now());
@@ -16930,17 +17023,17 @@ function MscTryOnProgress({ t: t2 }) {
16930
17023
  }
16931
17024
  }, 200);
16932
17025
  return () => clearInterval(id2);
16933
- }, [t2]);
17026
+ }, []);
16934
17027
  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-tryon-progress-wrap", children: [
16935
17028
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-tryon-progress-ring", children: [
16936
- /* @__PURE__ */ jsxRuntimeExports.jsxs("svg", { width: "48", height: "48", viewBox: "0 0 48 48", "aria-hidden": "true", children: [
16937
- /* @__PURE__ */ jsxRuntimeExports.jsx("circle", { cx: "24", cy: "24", r: MSC_RING_RADIUS, className: "ps-tryon-progress-ring-track" }),
17029
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("svg", { width: "64", height: "64", viewBox: "0 0 64 64", "aria-hidden": "true", children: [
17030
+ /* @__PURE__ */ jsxRuntimeExports.jsx("circle", { cx: "32", cy: "32", r: MSC_RING_RADIUS, className: "ps-tryon-progress-ring-track" }),
16938
17031
  /* @__PURE__ */ jsxRuntimeExports.jsx(
16939
17032
  "circle",
16940
17033
  {
16941
17034
  ref: ringRef,
16942
- cx: "24",
16943
- cy: "24",
17035
+ cx: "32",
17036
+ cy: "32",
16944
17037
  r: MSC_RING_RADIUS,
16945
17038
  className: "ps-tryon-progress-ring-fill",
16946
17039
  strokeDasharray: MSC_RING_CIRC,
@@ -16989,11 +17082,12 @@ function MobileScanningView({
16989
17082
  };
16990
17083
  const [stageIdx, setStageIdx] = reactExports.useState(0);
16991
17084
  reactExports.useEffect(() => {
17085
+ const intervalMs = tryOnProcessing ? 2200 : 1500;
16992
17086
  const id2 = setInterval(() => {
16993
17087
  setStageIdx((i) => (i + 1) % stages.length);
16994
- }, 1500);
17088
+ }, intervalMs);
16995
17089
  return () => clearInterval(id2);
16996
- }, [stages.length]);
17090
+ }, [stages.length, tryOnProcessing]);
16997
17091
  reactExports.useEffect(() => {
16998
17092
  if (isPhotoMode && bodyLandmarks && stageIdx === 0) {
16999
17093
  setStageIdx(1);
@@ -17150,7 +17244,7 @@ function MultiSectionMobile({
17150
17244
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-msr-sections", children: sectionEntries.map(({ name, secResult }) => {
17151
17245
  const cleanName = name.replace(/\s*[—–-]\s*.*/g, "");
17152
17246
  const sec = secResult;
17153
- const sizeValue = sec.size || secResult.recommendedSize;
17247
+ const sizeValue = sec.found === false ? t2("No fit") : sec.size || secResult.recommendedSize;
17154
17248
  return /* @__PURE__ */ jsxRuntimeExports.jsxs(
17155
17249
  "button",
17156
17250
  {
@@ -17185,7 +17279,19 @@ function MultiSectionMobile({
17185
17279
  children: t2("Continue Shopping")
17186
17280
  }
17187
17281
  )
17188
- ] }) : /* @__PURE__ */ jsxRuntimeExports.jsxs(
17282
+ ] }) : sizingResult?.found === false ? (
17283
+ // Backend couldn't find a size that fits — Try-On is meaningless
17284
+ // without a recommendation, so surface a clear terminal action.
17285
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
17286
+ "button",
17287
+ {
17288
+ type: "button",
17289
+ className: "ps-msr-tryon-cta",
17290
+ onClick: onClose,
17291
+ children: t2("Continue Shopping")
17292
+ }
17293
+ )
17294
+ ) : /* @__PURE__ */ jsxRuntimeExports.jsxs(
17189
17295
  "button",
17190
17296
  {
17191
17297
  type: "button",
@@ -17218,7 +17324,7 @@ const SKELETON_CONNECTIONS = [
17218
17324
  ["rightKnee", "rightAnkle"]
17219
17325
  ];
17220
17326
  const TRYON_TARGET_SECONDS = 22;
17221
- const TRYON_RING_RADIUS = 20;
17327
+ const TRYON_RING_RADIUS = 27;
17222
17328
  const TRYON_RING_CIRC = 2 * Math.PI * TRYON_RING_RADIUS;
17223
17329
  function TryOnProgress({ t: t2, isActive }) {
17224
17330
  const startRef = reactExports.useRef(null);
@@ -17248,18 +17354,18 @@ function TryOnProgress({ t: t2, isActive }) {
17248
17354
  }
17249
17355
  }, 200);
17250
17356
  return () => clearInterval(id2);
17251
- }, [isActive, t2]);
17357
+ }, [isActive]);
17252
17358
  if (!isActive) return null;
17253
17359
  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-tryon-progress-wrap", children: [
17254
17360
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-tryon-progress-ring", children: [
17255
- /* @__PURE__ */ jsxRuntimeExports.jsxs("svg", { width: "48", height: "48", viewBox: "0 0 48 48", "aria-hidden": "true", children: [
17256
- /* @__PURE__ */ jsxRuntimeExports.jsx("circle", { cx: "24", cy: "24", r: TRYON_RING_RADIUS, className: "ps-tryon-progress-ring-track" }),
17361
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("svg", { width: "64", height: "64", viewBox: "0 0 64 64", "aria-hidden": "true", children: [
17362
+ /* @__PURE__ */ jsxRuntimeExports.jsx("circle", { cx: "32", cy: "32", r: TRYON_RING_RADIUS, className: "ps-tryon-progress-ring-track" }),
17257
17363
  /* @__PURE__ */ jsxRuntimeExports.jsx(
17258
17364
  "circle",
17259
17365
  {
17260
17366
  ref: ringRef,
17261
- cx: "24",
17262
- cy: "24",
17367
+ cx: "32",
17368
+ cy: "32",
17263
17369
  r: TRYON_RING_RADIUS,
17264
17370
  className: "ps-tryon-progress-ring-fill",
17265
17371
  strokeDasharray: TRYON_RING_CIRC,
@@ -17410,11 +17516,12 @@ function StageCycler({
17410
17516
  }, [tryOnProcessing]);
17411
17517
  reactExports.useEffect(() => {
17412
17518
  if (isDone) return;
17519
+ const intervalMs = tryOnProcessing ? 2200 : 900;
17413
17520
  const id2 = setInterval(() => {
17414
17521
  setIdx((i) => Math.min(i + 1, active.length - 1));
17415
- }, 900);
17522
+ }, intervalMs);
17416
17523
  return () => clearInterval(id2);
17417
- }, [isDone, active.length]);
17524
+ }, [isDone, active.length, tryOnProcessing]);
17418
17525
  const current = active[idx] ?? active[0];
17419
17526
  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-msc-stage", style: { alignSelf: "center", marginTop: "auto", marginBottom: "auto" }, children: [
17420
17527
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-msc-stage-slot", children: [
@@ -17661,7 +17768,8 @@ function SectionDetailView({
17661
17768
  backLabel,
17662
17769
  internationalSizes,
17663
17770
  continueLabel,
17664
- renderRaw = false
17771
+ renderRaw = false,
17772
+ sectionFound
17665
17773
  }) {
17666
17774
  const recSize = sectionResult?.recommendedSize || "";
17667
17775
  const [selectedSize, setSelectedSize] = reactExports.useState(null);
@@ -17706,7 +17814,8 @@ function SectionDetailView({
17706
17814
  const hasBadFit = details.some((d) => BAD_FIT.test(d.fit || ""));
17707
17815
  return hasBadFit ? t2("Not Recommended") : t2("Your Selection");
17708
17816
  }, [isRecommended, sectionResult, t2]);
17709
- const displaySizeLabel = selectedCountry && isRecommended && internationalSizes && internationalSizes[selectedCountry] ? internationalSizes[selectedCountry] : displaySize;
17817
+ const noFitMessage = t2("We couldn't find a size that fits for this product");
17818
+ const displaySizeLabel = sectionFound === false ? noFitMessage : selectedCountry && isRecommended && internationalSizes && internationalSizes[selectedCountry] ? internationalSizes[selectedCountry] : displaySize;
17710
17819
  const columnUnits = reactExports.useMemo(() => {
17711
17820
  const units = [];
17712
17821
  for (let i = 0; i < section.headers.length; i++) {
@@ -18560,6 +18669,7 @@ function SizeResultView({
18560
18669
  const allDone = hasPhoto ? sizingDone && tryOnDone : sizingDone;
18561
18670
  const isMobile = useIsMobile();
18562
18671
  const isAccessory = measurementType === "face" || measurementType === "head";
18672
+ const noFit = sizingResult?.found === false;
18563
18673
  const vtoExcluded = measurementType === "foot";
18564
18674
  console.log("[PS-SDK] SizeResultView render:", {
18565
18675
  hasPhoto,
@@ -18644,6 +18754,7 @@ function SizeResultView({
18644
18754
  sectionName: entry.name,
18645
18755
  section: entry.section,
18646
18756
  sectionResult: entry.secResult,
18757
+ sectionFound: entry.secResult?.found,
18647
18758
  userMeasurements: entry.userMeasurements,
18648
18759
  unitLbl,
18649
18760
  chartUnit: resultUnit,
@@ -18693,6 +18804,7 @@ function SizeResultView({
18693
18804
  sectionName: entry.name,
18694
18805
  section: entry.section,
18695
18806
  sectionResult: entry.secResult,
18807
+ sectionFound: entry.secResult?.found,
18696
18808
  userMeasurements: entry.userMeasurements,
18697
18809
  unitLbl,
18698
18810
  chartUnit: resultUnit,
@@ -18814,7 +18926,7 @@ function SizeResultView({
18814
18926
  return /* @__PURE__ */ jsxRuntimeExports.jsxs("button", { className: `ps-tryon-sr-card-v2${isLast ? " ps-full" : ""}`, onClick: () => setActiveSection(name), style: { animationDelay: `${idx * 0.07}s` }, children: [
18815
18927
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-tryon-sr-card-v2-text", children: [
18816
18928
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "ps-tryon-sr-card-v2-label", children: name.replace(/\s*[—–-]\s*.*/g, "") }),
18817
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "ps-tryon-sr-card-v2-value", children: sec.size || secResult.recommendedSize }),
18929
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "ps-tryon-sr-card-v2-value", children: sec.found === false ? t2("No fit") : sec.size || secResult.recommendedSize }),
18818
18930
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "ps-tryon-sr-card-v2-rec", children: t2("recommended") })
18819
18931
  ] }),
18820
18932
  sectionImg && /* @__PURE__ */ jsxRuntimeExports.jsx("img", { src: sectionImg, alt: name, className: "ps-tryon-sr-card-v2-img" }),
@@ -18838,7 +18950,7 @@ function SizeResultView({
18838
18950
  " →"
18839
18951
  ]
18840
18952
  }
18841
- ) : vtoExcluded ? /* @__PURE__ */ jsxRuntimeExports.jsxs(
18953
+ ) : vtoExcluded || noFit ? /* @__PURE__ */ jsxRuntimeExports.jsxs(
18842
18954
  "button",
18843
18955
  {
18844
18956
  className: "ps-tryon-v2-cta",
@@ -18891,6 +19003,7 @@ function SizeResultView({
18891
19003
  sectionName,
18892
19004
  section: singleSection,
18893
19005
  sectionResult: singleResult,
19006
+ sectionFound: sizingResult?.found,
18894
19007
  userMeasurements: singleUserMeasurements,
18895
19008
  unitLbl,
18896
19009
  chartUnit: resultUnit,
@@ -18898,7 +19011,7 @@ function SizeResultView({
18898
19011
  onBack: resultImageUrl ? onClose || (() => setView("body-profile")) : () => setView("body-profile"),
18899
19012
  backLabel: t2("Back"),
18900
19013
  internationalSizes: sizingResult?.internationalSizes,
18901
- onTryOn: resultImageUrl || vtoExcluded ? void 0 : handleSingleTryOn,
19014
+ onTryOn: resultImageUrl || vtoExcluded || noFit ? void 0 : handleSingleTryOn,
18902
19015
  continueLabel: resultImageUrl ? t2("Continue Shopping") : void 0,
18903
19016
  tryOnProcessing,
18904
19017
  productImage: resultImageUrl || productImage,
@@ -18938,6 +19051,7 @@ function SizeResultView({
18938
19051
  sectionName,
18939
19052
  section: singleSection,
18940
19053
  sectionResult: singleResult,
19054
+ sectionFound: sizingResult?.found,
18941
19055
  userMeasurements: singleUserMeasurements,
18942
19056
  unitLbl,
18943
19057
  chartUnit: resultUnit,
@@ -18945,7 +19059,7 @@ function SizeResultView({
18945
19059
  onBack: resultImageUrl ? onClose || (() => setView("body-profile")) : () => setView("body-profile"),
18946
19060
  backLabel: t2("Back"),
18947
19061
  internationalSizes: sizingResult?.internationalSizes,
18948
- onTryOn: resultImageUrl || vtoExcluded ? void 0 : handleSingleTryOn,
19062
+ onTryOn: resultImageUrl || vtoExcluded || noFit ? void 0 : handleSingleTryOn,
18949
19063
  continueLabel: resultImageUrl ? t2("Continue Shopping") : void 0,
18950
19064
  tryOnProcessing,
18951
19065
  t: t2,
@@ -19321,7 +19435,7 @@ function UploadView({
19321
19435
  }
19322
19436
  ) });
19323
19437
  }
19324
- const RING_RADIUS = 20;
19438
+ const RING_RADIUS = 27;
19325
19439
  const RING_CIRCUMFERENCE = 2 * Math.PI * RING_RADIUS;
19326
19440
  function ProcessingView({
19327
19441
  previewUrl,
@@ -19366,12 +19480,12 @@ function ProcessingView({
19366
19480
  ] }),
19367
19481
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-tryon-progress-section", children: [
19368
19482
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-tryon-progress-ring", children: [
19369
- /* @__PURE__ */ jsxRuntimeExports.jsxs("svg", { viewBox: "0 0 48 48", width: "48", height: "48", "aria-hidden": "true", children: [
19483
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("svg", { viewBox: "0 0 64 64", width: "64", height: "64", "aria-hidden": "true", children: [
19370
19484
  /* @__PURE__ */ jsxRuntimeExports.jsx(
19371
19485
  "circle",
19372
19486
  {
19373
- cx: "24",
19374
- cy: "24",
19487
+ cx: "32",
19488
+ cy: "32",
19375
19489
  r: RING_RADIUS,
19376
19490
  className: "ps-tryon-progress-ring-track"
19377
19491
  }
@@ -19380,8 +19494,8 @@ function ProcessingView({
19380
19494
  "circle",
19381
19495
  {
19382
19496
  ref: ringCb,
19383
- cx: "24",
19384
- cy: "24",
19497
+ cx: "32",
19498
+ cy: "32",
19385
19499
  r: RING_RADIUS,
19386
19500
  className: "ps-tryon-progress-ring-fill",
19387
19501
  strokeDasharray: RING_CIRCUMFERENCE,
@@ -21951,7 +22065,7 @@ function BodyProfileView({
21951
22065
  hidePhotoOptions: hasActiveProfileWithMeasurements,
21952
22066
  onNext: hasActiveProfileWithMeasurements && onUseActiveProfile ? onUseActiveProfile : handleNext,
21953
22067
  canProceed: true,
21954
- fastPathLabel: hasActiveProfileWithMeasurements ? t2("Find My Best Fit") : void 0,
22068
+ fastPathLabel: hasActiveProfileWithMeasurements ? t2("Find My Best Fit") + (getUnitLabel(hUnit) ? ` (${getUnitLabel(hUnit)})` : "") : void 0,
21955
22069
  activeProfileName: hasActiveProfileWithMeasurements ? activeProfileName : null,
21956
22070
  onStartFresh,
21957
22071
  error,
@@ -22206,7 +22320,8 @@ function BodyProfileView({
22206
22320
  !(isMobile && step === "basics") && (() => {
22207
22321
  const useProfileFast = step === "basics" && hasActiveProfileWithMeasurements && !!onUseActiveProfile;
22208
22322
  const handleClick = useProfileFast ? onUseActiveProfile : handleNext;
22209
- const label = useProfileFast ? t2("Find My Best Fit") : isLastStep ? t2("Find My Size") : t2("Next");
22323
+ const unitSuffix = getUnitLabel(hUnit) ? ` (${getUnitLabel(hUnit)})` : "";
22324
+ const label = useProfileFast ? t2("Find My Best Fit") + unitSuffix : isLastStep ? t2("Find My Size") + unitSuffix : t2("Next");
22210
22325
  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-bp-nav", children: [
22211
22326
  step !== "basics" ? /* @__PURE__ */ jsxRuntimeExports.jsxs("button", { className: "ps-bp-back-btn", onClick: handleBackStep, type: "button", children: [
22212
22327
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "ps-bp-back-arrow", children: "←" }),
@@ -22396,6 +22511,7 @@ function AccessorySizeView({
22396
22511
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-bpm-bottom", children: [
22397
22512
  /* @__PURE__ */ jsxRuntimeExports.jsxs("button", { type: "button", className: "ps-bpm-next-btn", onClick: handleManualSubmit, children: [
22398
22513
  t2("Find My Size"),
22514
+ getUnitLabel(sizingUnit) ? ` (${getUnitLabel(sizingUnit)})` : "",
22399
22515
  " ",
22400
22516
  /* @__PURE__ */ jsxRuntimeExports.jsx(ArrowRightIcon, {})
22401
22517
  ] }),
@@ -22730,6 +22846,7 @@ function AccessorySizeView({
22730
22846
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", {}),
22731
22847
  /* @__PURE__ */ jsxRuntimeExports.jsxs("button", { className: "ps-bp-next-btn", onClick: handleManualSubmit, type: "button", children: [
22732
22848
  t2("Find My Size"),
22849
+ getUnitLabel(sizingUnit) ? ` (${getUnitLabel(sizingUnit)})` : "",
22733
22850
  " ",
22734
22851
  /* @__PURE__ */ jsxRuntimeExports.jsx(ArrowRightIcon, {})
22735
22852
  ] })
@@ -22963,6 +23080,9 @@ function PrimeStyleTryonInner({
22963
23080
  const [sizingUnit, setSizingUnit] = reactExports.useState(imperial ? "in" : "cm");
22964
23081
  const [heightUnit, setHeightUnit] = reactExports.useState(imperial ? "in" : "cm");
22965
23082
  const [weightUnit, setWeightUnit] = reactExports.useState(imperial ? "lbs" : "kg");
23083
+ reactExports.useEffect(() => {
23084
+ if (detectMeasurementType(productTitle) === "foot") setSizingUnit("cm");
23085
+ }, [productTitle]);
22966
23086
  const formRef = reactExports.useRef({});
22967
23087
  const [formGender, setFormGender] = reactExports.useState("male");
22968
23088
  const [formKey, setFormKey] = reactExports.useState(0);
@@ -23037,7 +23157,7 @@ function PrimeStyleTryonInner({
23037
23157
  { at: 75, text: t2("Refining details...") },
23038
23158
  { at: 90, text: t2("Almost there...") }
23039
23159
  ];
23040
- const RING_CIRCUMFERENCE2 = 2 * Math.PI * 20;
23160
+ const RING_CIRCUMFERENCE2 = 2 * Math.PI * 27;
23041
23161
  progressIntervalRef.current = setInterval(() => {
23042
23162
  if (completedRef.current) return;
23043
23163
  const startTs = progressStartTsRef.current || Date.now();
@@ -23215,13 +23335,10 @@ function PrimeStyleTryonInner({
23215
23335
  [activeProfileId, profiles, apiUrl, productImage, productTitle, effectiveProductId, setActiveProfileId$1]
23216
23336
  );
23217
23337
  const snapSubmitRef = reactExports.useRef(null);
23218
- const handleUseActiveProfile = reactExports.useCallback(async () => {
23219
- const p2 = profiles.find((x2) => x2.id === activeProfileId);
23220
- if (!p2) return;
23338
+ const [confirmProfile, setConfirmProfile] = reactExports.useState(null);
23339
+ const runRecommendWithProfile = reactExports.useCallback(async (p2) => {
23221
23340
  const profileHeight = p2.height ?? p2.heightCm ?? 0;
23222
23341
  const profileWeight = p2.weight ?? p2.weightKg ?? 0;
23223
- const hasIdentity = profileHeight > 0 && profileWeight > 0;
23224
- if (!hasIdentity) return;
23225
23342
  const hasStored = !!p2.measurements && Object.keys(p2.measurements).length > 0;
23226
23343
  const storedPhoto = p2.photoBase64;
23227
23344
  if (!hasStored && storedPhoto && profileHeight > 0 && snapSubmitRef.current) {
@@ -23245,9 +23362,8 @@ function PrimeStyleTryonInner({
23245
23362
  }
23246
23363
  setSizingResult(null);
23247
23364
  setSizingLoading(true);
23248
- const hasStoredMeasurements = !!p2.measurements && Object.keys(p2.measurements).length > 0;
23249
- setEstimationDone(hasStoredMeasurements);
23250
- if (hasStoredMeasurements) {
23365
+ setEstimationDone(hasStored);
23366
+ if (hasStored) {
23251
23367
  setPreviewUrl(null);
23252
23368
  setBodyLandmarks(null);
23253
23369
  }
@@ -23280,7 +23396,27 @@ function PrimeStyleTryonInner({
23280
23396
  setEstimationDone(true);
23281
23397
  }).catch(() => {
23282
23398
  }).finally(() => setSizingLoading(false));
23283
- }, [profiles, activeProfileId, effectiveProductId, productTitle, productImage, sizeGuideData, apiUrl]);
23399
+ }, [effectiveProductId, productTitle, productImage, sizeGuideData, apiUrl, previewUrl]);
23400
+ const handleUseActiveProfile = reactExports.useCallback(async () => {
23401
+ const p2 = profiles.find((x2) => x2.id === activeProfileId);
23402
+ if (!p2) return;
23403
+ const profileHeight = p2.height ?? p2.heightCm ?? 0;
23404
+ const profileWeight = p2.weight ?? p2.weightKg ?? 0;
23405
+ const hasIdentity = profileHeight > 0 && profileWeight > 0;
23406
+ if (!hasIdentity) return;
23407
+ setConfirmProfile(p2);
23408
+ }, [profiles, activeProfileId]);
23409
+ const proceedFromConfirmProfile = reactExports.useCallback(() => {
23410
+ if (!confirmProfile) return;
23411
+ const p2 = confirmProfile;
23412
+ setConfirmProfile(null);
23413
+ void runRecommendWithProfile(p2);
23414
+ }, [confirmProfile, runRecommendWithProfile]);
23415
+ const cancelFromConfirmProfile = reactExports.useCallback(() => {
23416
+ setConfirmProfile(null);
23417
+ setFormKey((k2) => k2 + 1);
23418
+ setView("body-profile");
23419
+ }, []);
23284
23420
  const applyProfileRef = reactExports.useRef(() => {
23285
23421
  });
23286
23422
  const handleOpen = reactExports.useCallback(() => {
@@ -24655,6 +24791,15 @@ function PrimeStyleTryonInner({
24655
24791
  onCancel: () => setDeleteConfirmId(null),
24656
24792
  t: t2
24657
24793
  }
24794
+ ),
24795
+ confirmProfile && /* @__PURE__ */ jsxRuntimeExports.jsx(
24796
+ ConfirmMeasurementsModal,
24797
+ {
24798
+ profile: confirmProfile,
24799
+ onProceed: proceedFromConfirmProfile,
24800
+ onEdit: cancelFromConfirmProfile,
24801
+ t: t2
24802
+ }
24658
24803
  )
24659
24804
  ] }) }),
24660
24805
  document.body
package/dist/types.d.ts CHANGED
@@ -198,6 +198,8 @@ export interface MatchDetail {
198
198
  export interface SectionRecommendation {
199
199
  recommendedSize: string;
200
200
  matchDetails: MatchDetail[];
201
+ /** True when the user's measurements fit inside this section's chart. */
202
+ found?: boolean;
201
203
  }
202
204
  /** Full sizing recommendation result */
203
205
  export interface SizingResult {
@@ -213,6 +215,11 @@ export interface SizingResult {
213
215
  sections?: Record<string, SectionRecommendation>;
214
216
  /** Unit the matchDetails values (and chart columns used for matching) are in. */
215
217
  unit?: "cm" | "in";
218
+ /** True when the backend found a size whose measurements actually fit the
219
+ * user. False when the user's body is outside every row in the chart —
220
+ * the SDK renders a "no size" message and disables Try-On. Optional for
221
+ * backwards compatibility with older backends (treat `undefined` as true). */
222
+ found?: boolean;
216
223
  }
217
224
  /** Fit info for a body area — tells Gemini how the garment should render at this region */
218
225
  export interface FitAreaInfo {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@primestyleai/tryon",
3
- "version": "5.9.1",
3
+ "version": "5.10.0",
4
4
  "description": "PrimeStyle Virtual Try-On SDK — React component & Web Component",
5
5
  "type": "module",
6
6
  "main": "dist/primestyle-tryon.js",