@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.
@@ -0,0 +1,22 @@
1
+ import type { TranslateFn } from "../../i18n";
2
+ import type { Profile } from "../types";
3
+ /**
4
+ * Confirmation modal shown before the "Find My Best Fit" fast path fires a
5
+ * recommendation against the user's stored profile. The user sees exactly
6
+ * what measurements we're about to send (and in which unit system) so they
7
+ * can catch an imperial vs metric mismatch before the API call.
8
+ *
9
+ * - Proceed → parent runs `recommendForProduct`
10
+ * - Edit / close-X → parent returns to the basics step with the profile
11
+ * values intact, ready to be edited and resubmitted.
12
+ *
13
+ * Reuses the existing `.ps-confirm-overlay` / `.ps-confirm-modal` CSS already
14
+ * shipped for `DeleteConfirmModal` in `MySizingProfilesView` — same visual
15
+ * language, no new CSS class to maintain.
16
+ */
17
+ export declare function ConfirmMeasurementsModal({ profile, onProceed, onEdit, t, }: {
18
+ profile: Profile;
19
+ onProceed: () => void;
20
+ onEdit: () => void;
21
+ t: TranslateFn;
22
+ }): import("react/jsx-runtime").JSX.Element;
@@ -655,7 +655,8 @@ async function recommendForProduct(input) {
655
655
  sections: sectionsMap,
656
656
  profileId: profile.id,
657
657
  fromCache: false,
658
- raw: result
658
+ raw: result,
659
+ found: result.found
659
660
  };
660
661
  }
661
662
  async function estimateFullMeasurements(args) {
@@ -714,6 +715,12 @@ async function estimateFullMeasurements(args) {
714
715
  function isImperial(locale) {
715
716
  return ["US", "UK", "AU"].includes(locale);
716
717
  }
718
+ function getUnitLabel(unit) {
719
+ if (unit === "in" || unit === "inches" || unit === "lbs") return "Imperial";
720
+ if (unit === "cm" || unit === "kg") return "Metric";
721
+ if (unit === "mm") return "mm";
722
+ return "";
723
+ }
717
724
  function cx(base, override) {
718
725
  return override ? `${base} ${override}` : base;
719
726
  }
@@ -2099,21 +2106,21 @@ const STYLES = `
2099
2106
  /* Shared progress layout used inside StageCycler (desktop) and
2100
2107
  MobileScanningView — row of ring + bar + percent, same tokens. */
2101
2108
  .ps-tryon-progress-wrap {
2102
- display: flex; align-items: center; gap: 10px;
2103
- width: 100%; max-width: 320px; margin-top: 16px;
2109
+ display: flex; align-items: center; gap: 12px;
2110
+ width: 100%; max-width: 360px; margin-top: 18px;
2104
2111
  }
2105
2112
  .ps-tryon-progress-wrap .ps-tryon-progress-bar-wrap {
2106
- flex: 1; height: 4px; border-radius: 3px; overflow: hidden;
2113
+ flex: 1; height: 6px; border-radius: 4px; overflow: hidden;
2107
2114
  position: relative; background: var(--ps-border-color);
2108
2115
  }
2109
2116
  .ps-tryon-progress-wrap .ps-tryon-progress-bar-fill {
2110
2117
  height: 100%; width: 0%;
2111
2118
  background: linear-gradient(90deg, var(--ps-accent), var(--ps-accent-light));
2112
- border-radius: 3px; transition: width 0.3s ease;
2119
+ border-radius: 4px; transition: width 0.3s ease;
2113
2120
  }
2114
2121
  .ps-tryon-progress-wrap .ps-tryon-progress-pct {
2115
- font-size: 11px; font-weight: 700; color: var(--ps-accent);
2116
- min-width: 30px; text-align: right;
2122
+ font-size: 13px; font-weight: 700; color: var(--ps-accent);
2123
+ min-width: 36px; text-align: right;
2117
2124
  font-variant-numeric: tabular-nums;
2118
2125
  }
2119
2126
  .ps-tryon-progress-bar-wrap {
@@ -2141,25 +2148,25 @@ const STYLES = `
2141
2148
  font-variant-numeric: tabular-nums;
2142
2149
  }
2143
2150
 
2144
- /* Circular ETA ring — 48×48 px SVG with a track + progress circle; ETA
2151
+ /* Circular ETA ring — 64×64 px SVG with a track + progress circle; ETA
2145
2152
  text centered. strokeDashoffset is driven by the ticker in
2146
2153
  PrimeStyleTryonInner, so CSS only styles the appearance. */
2147
2154
  .ps-tryon-progress-ring {
2148
- position: relative; width: 48px; height: 48px; flex: 0 0 48px;
2155
+ position: relative; width: 64px; height: 64px; flex: 0 0 64px;
2149
2156
  display: flex; align-items: center; justify-content: center;
2150
2157
  }
2151
2158
  .ps-tryon-progress-ring svg { transform: rotate(-90deg); }
2152
2159
  .ps-tryon-progress-ring-track {
2153
- fill: none; stroke: var(--ps-border-color); stroke-width: 3.5;
2160
+ fill: none; stroke: var(--ps-border-color); stroke-width: 5;
2154
2161
  }
2155
2162
  .ps-tryon-progress-ring-fill {
2156
- fill: none; stroke: var(--ps-accent); stroke-width: 3.5;
2163
+ fill: none; stroke: var(--ps-accent); stroke-width: 5;
2157
2164
  stroke-linecap: round;
2158
2165
  transition: stroke-dashoffset 0.3s ease;
2159
2166
  }
2160
2167
  .ps-tryon-progress-eta {
2161
2168
  position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
2162
- font-size: 10px; font-weight: 700; color: var(--ps-accent);
2169
+ font-size: 13px; font-weight: 700; color: var(--ps-accent);
2163
2170
  font-variant-numeric: tabular-nums; letter-spacing: 0.01em;
2164
2171
  pointer-events: none;
2165
2172
  }
@@ -7246,6 +7253,92 @@ function ProfileDetailModal({
7246
7253
  document.body
7247
7254
  );
7248
7255
  }
7256
+ function ConfirmMeasurementsModal({
7257
+ profile,
7258
+ onProceed,
7259
+ onEdit,
7260
+ t
7261
+ }) {
7262
+ const heightUnit = profile.heightUnit === "in" || profile.heightUnit === "ft" ? "in" : "cm";
7263
+ const weightUnit = profile.weightUnit === "lbs" ? "lbs" : "kg";
7264
+ const systemLabel = getUnitLabel(heightUnit);
7265
+ const formatHeight = (h) => {
7266
+ if (!h) return "—";
7267
+ if (heightUnit === "in") {
7268
+ const ft = Math.floor(h / 12);
7269
+ const inches = Math.round(h % 12);
7270
+ return `${ft}'${inches}"`;
7271
+ }
7272
+ return `${Math.round(h)} cm`;
7273
+ };
7274
+ const formatWeight = (w) => {
7275
+ if (!w) return "—";
7276
+ return `${Math.round(w)} ${weightUnit}`;
7277
+ };
7278
+ const height = profile.height ?? profile.heightCm;
7279
+ const weight = profile.weight ?? profile.weightKg;
7280
+ return /* @__PURE__ */ jsx("div", { className: "ps-confirm-overlay", onClick: onEdit, children: /* @__PURE__ */ jsxs("div", { className: "ps-confirm-modal", onClick: (e) => e.stopPropagation(), children: [
7281
+ /* @__PURE__ */ jsx(
7282
+ "button",
7283
+ {
7284
+ type: "button",
7285
+ "aria-label": t("Close"),
7286
+ onClick: onEdit,
7287
+ style: {
7288
+ position: "absolute",
7289
+ top: "0.75vw",
7290
+ right: "0.75vw",
7291
+ width: "1.8vw",
7292
+ height: "1.8vw",
7293
+ borderRadius: "50%",
7294
+ background: "transparent",
7295
+ border: "none",
7296
+ cursor: "pointer",
7297
+ display: "flex",
7298
+ alignItems: "center",
7299
+ justifyContent: "center",
7300
+ color: "var(--ps-text-muted)"
7301
+ },
7302
+ children: /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", width: "16", height: "16", children: [
7303
+ /* @__PURE__ */ jsx("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
7304
+ /* @__PURE__ */ jsx("line", { x1: "6", y1: "6", x2: "18", y2: "18" })
7305
+ ] })
7306
+ }
7307
+ ),
7308
+ /* @__PURE__ */ jsx("p", { style: { fontWeight: 700, marginBottom: "0.4vw" }, children: t("Confirm your measurements") }),
7309
+ /* @__PURE__ */ jsx("small", { style: { color: "var(--ps-text-muted)" }, children: systemLabel ? t("You chose") + " " + systemLabel + ". " + t("Review before continuing.") : t("Review before continuing.") }),
7310
+ /* @__PURE__ */ jsxs("ul", { style: {
7311
+ listStyle: "none",
7312
+ padding: 0,
7313
+ margin: "0.8vw 0 0.2vw 0",
7314
+ width: "100%",
7315
+ textAlign: "left",
7316
+ fontSize: "0.78vw",
7317
+ lineHeight: 1.6
7318
+ }, children: [
7319
+ /* @__PURE__ */ jsxs("li", { style: { display: "flex", justifyContent: "space-between", gap: "1vw" }, children: [
7320
+ /* @__PURE__ */ jsx("span", { style: { color: "var(--ps-text-muted)" }, children: t("Height") }),
7321
+ /* @__PURE__ */ jsx("span", { style: { fontWeight: 600 }, children: formatHeight(height) })
7322
+ ] }),
7323
+ /* @__PURE__ */ jsxs("li", { style: { display: "flex", justifyContent: "space-between", gap: "1vw" }, children: [
7324
+ /* @__PURE__ */ jsx("span", { style: { color: "var(--ps-text-muted)" }, children: t("Weight") }),
7325
+ /* @__PURE__ */ jsx("span", { style: { fontWeight: 600 }, children: formatWeight(weight) })
7326
+ ] }),
7327
+ profile.age ? /* @__PURE__ */ jsxs("li", { style: { display: "flex", justifyContent: "space-between", gap: "1vw" }, children: [
7328
+ /* @__PURE__ */ jsx("span", { style: { color: "var(--ps-text-muted)" }, children: t("Age") }),
7329
+ /* @__PURE__ */ jsx("span", { style: { fontWeight: 600 }, children: profile.age })
7330
+ ] }) : null,
7331
+ /* @__PURE__ */ jsxs("li", { style: { display: "flex", justifyContent: "space-between", gap: "1vw" }, children: [
7332
+ /* @__PURE__ */ jsx("span", { style: { color: "var(--ps-text-muted)" }, children: t("Gender") }),
7333
+ /* @__PURE__ */ jsx("span", { style: { fontWeight: 600 }, children: profile.gender === "female" ? t("Female") : t("Male") })
7334
+ ] })
7335
+ ] }),
7336
+ /* @__PURE__ */ jsxs("div", { className: "ps-confirm-actions", children: [
7337
+ /* @__PURE__ */ jsx("button", { type: "button", className: "ps-confirm-cancel", onClick: onEdit, children: t("Edit") }),
7338
+ /* @__PURE__ */ jsx("button", { type: "button", className: "ps-confirm-delete", onClick: onProceed, children: t("Proceed") })
7339
+ ] })
7340
+ ] }) });
7341
+ }
7249
7342
  function WelcomeView({
7250
7343
  productImage,
7251
7344
  setView,
@@ -7482,7 +7575,7 @@ function MobileSkeleton({ landmarks, w, h }) {
7482
7575
  );
7483
7576
  }
7484
7577
  const MSC_TRYON_TARGET_SECONDS = 22;
7485
- const MSC_RING_RADIUS = 20;
7578
+ const MSC_RING_RADIUS = 27;
7486
7579
  const MSC_RING_CIRC = 2 * Math.PI * MSC_RING_RADIUS;
7487
7580
  function MscTryOnProgress({ t }) {
7488
7581
  const startRef = useRef(Date.now());
@@ -7505,17 +7598,17 @@ function MscTryOnProgress({ t }) {
7505
7598
  }
7506
7599
  }, 200);
7507
7600
  return () => clearInterval(id);
7508
- }, [t]);
7601
+ }, []);
7509
7602
  return /* @__PURE__ */ jsxs("div", { className: "ps-tryon-progress-wrap", children: [
7510
7603
  /* @__PURE__ */ jsxs("div", { className: "ps-tryon-progress-ring", children: [
7511
- /* @__PURE__ */ jsxs("svg", { width: "48", height: "48", viewBox: "0 0 48 48", "aria-hidden": "true", children: [
7512
- /* @__PURE__ */ jsx("circle", { cx: "24", cy: "24", r: MSC_RING_RADIUS, className: "ps-tryon-progress-ring-track" }),
7604
+ /* @__PURE__ */ jsxs("svg", { width: "64", height: "64", viewBox: "0 0 64 64", "aria-hidden": "true", children: [
7605
+ /* @__PURE__ */ jsx("circle", { cx: "32", cy: "32", r: MSC_RING_RADIUS, className: "ps-tryon-progress-ring-track" }),
7513
7606
  /* @__PURE__ */ jsx(
7514
7607
  "circle",
7515
7608
  {
7516
7609
  ref: ringRef,
7517
- cx: "24",
7518
- cy: "24",
7610
+ cx: "32",
7611
+ cy: "32",
7519
7612
  r: MSC_RING_RADIUS,
7520
7613
  className: "ps-tryon-progress-ring-fill",
7521
7614
  strokeDasharray: MSC_RING_CIRC,
@@ -7564,11 +7657,12 @@ function MobileScanningView({
7564
7657
  };
7565
7658
  const [stageIdx, setStageIdx] = useState(0);
7566
7659
  useEffect(() => {
7660
+ const intervalMs = tryOnProcessing ? 2200 : 1500;
7567
7661
  const id = setInterval(() => {
7568
7662
  setStageIdx((i) => (i + 1) % stages.length);
7569
- }, 1500);
7663
+ }, intervalMs);
7570
7664
  return () => clearInterval(id);
7571
- }, [stages.length]);
7665
+ }, [stages.length, tryOnProcessing]);
7572
7666
  useEffect(() => {
7573
7667
  if (isPhotoMode && bodyLandmarks && stageIdx === 0) {
7574
7668
  setStageIdx(1);
@@ -7725,7 +7819,7 @@ function MultiSectionMobile({
7725
7819
  /* @__PURE__ */ jsx("div", { className: "ps-msr-sections", children: sectionEntries.map(({ name, secResult }) => {
7726
7820
  const cleanName = name.replace(/\s*[—–-]\s*.*/g, "");
7727
7821
  const sec = secResult;
7728
- const sizeValue = sec.size || secResult.recommendedSize;
7822
+ const sizeValue = sec.found === false ? t("No fit") : sec.size || secResult.recommendedSize;
7729
7823
  return /* @__PURE__ */ jsxs(
7730
7824
  "button",
7731
7825
  {
@@ -7760,7 +7854,19 @@ function MultiSectionMobile({
7760
7854
  children: t("Continue Shopping")
7761
7855
  }
7762
7856
  )
7763
- ] }) : /* @__PURE__ */ jsxs(
7857
+ ] }) : sizingResult?.found === false ? (
7858
+ // Backend couldn't find a size that fits — Try-On is meaningless
7859
+ // without a recommendation, so surface a clear terminal action.
7860
+ /* @__PURE__ */ jsx(
7861
+ "button",
7862
+ {
7863
+ type: "button",
7864
+ className: "ps-msr-tryon-cta",
7865
+ onClick: onClose,
7866
+ children: t("Continue Shopping")
7867
+ }
7868
+ )
7869
+ ) : /* @__PURE__ */ jsxs(
7764
7870
  "button",
7765
7871
  {
7766
7872
  type: "button",
@@ -7793,7 +7899,7 @@ const SKELETON_CONNECTIONS = [
7793
7899
  ["rightKnee", "rightAnkle"]
7794
7900
  ];
7795
7901
  const TRYON_TARGET_SECONDS = 22;
7796
- const TRYON_RING_RADIUS = 20;
7902
+ const TRYON_RING_RADIUS = 27;
7797
7903
  const TRYON_RING_CIRC = 2 * Math.PI * TRYON_RING_RADIUS;
7798
7904
  function TryOnProgress({ t, isActive }) {
7799
7905
  const startRef = useRef(null);
@@ -7823,18 +7929,18 @@ function TryOnProgress({ t, isActive }) {
7823
7929
  }
7824
7930
  }, 200);
7825
7931
  return () => clearInterval(id);
7826
- }, [isActive, t]);
7932
+ }, [isActive]);
7827
7933
  if (!isActive) return null;
7828
7934
  return /* @__PURE__ */ jsxs("div", { className: "ps-tryon-progress-wrap", children: [
7829
7935
  /* @__PURE__ */ jsxs("div", { className: "ps-tryon-progress-ring", children: [
7830
- /* @__PURE__ */ jsxs("svg", { width: "48", height: "48", viewBox: "0 0 48 48", "aria-hidden": "true", children: [
7831
- /* @__PURE__ */ jsx("circle", { cx: "24", cy: "24", r: TRYON_RING_RADIUS, className: "ps-tryon-progress-ring-track" }),
7936
+ /* @__PURE__ */ jsxs("svg", { width: "64", height: "64", viewBox: "0 0 64 64", "aria-hidden": "true", children: [
7937
+ /* @__PURE__ */ jsx("circle", { cx: "32", cy: "32", r: TRYON_RING_RADIUS, className: "ps-tryon-progress-ring-track" }),
7832
7938
  /* @__PURE__ */ jsx(
7833
7939
  "circle",
7834
7940
  {
7835
7941
  ref: ringRef,
7836
- cx: "24",
7837
- cy: "24",
7942
+ cx: "32",
7943
+ cy: "32",
7838
7944
  r: TRYON_RING_RADIUS,
7839
7945
  className: "ps-tryon-progress-ring-fill",
7840
7946
  strokeDasharray: TRYON_RING_CIRC,
@@ -7985,11 +8091,12 @@ function StageCycler({
7985
8091
  }, [tryOnProcessing]);
7986
8092
  useEffect(() => {
7987
8093
  if (isDone) return;
8094
+ const intervalMs = tryOnProcessing ? 2200 : 900;
7988
8095
  const id = setInterval(() => {
7989
8096
  setIdx((i) => Math.min(i + 1, active.length - 1));
7990
- }, 900);
8097
+ }, intervalMs);
7991
8098
  return () => clearInterval(id);
7992
- }, [isDone, active.length]);
8099
+ }, [isDone, active.length, tryOnProcessing]);
7993
8100
  const current = active[idx] ?? active[0];
7994
8101
  return /* @__PURE__ */ jsxs("div", { className: "ps-msc-stage", style: { alignSelf: "center", marginTop: "auto", marginBottom: "auto" }, children: [
7995
8102
  /* @__PURE__ */ jsxs("div", { className: "ps-msc-stage-slot", children: [
@@ -8236,7 +8343,8 @@ function SectionDetailView({
8236
8343
  backLabel,
8237
8344
  internationalSizes,
8238
8345
  continueLabel,
8239
- renderRaw = false
8346
+ renderRaw = false,
8347
+ sectionFound
8240
8348
  }) {
8241
8349
  const recSize = sectionResult?.recommendedSize || "";
8242
8350
  const [selectedSize, setSelectedSize] = useState(null);
@@ -8281,7 +8389,8 @@ function SectionDetailView({
8281
8389
  const hasBadFit = details.some((d) => BAD_FIT.test(d.fit || ""));
8282
8390
  return hasBadFit ? t("Not Recommended") : t("Your Selection");
8283
8391
  }, [isRecommended, sectionResult, t]);
8284
- const displaySizeLabel = selectedCountry && isRecommended && internationalSizes && internationalSizes[selectedCountry] ? internationalSizes[selectedCountry] : displaySize;
8392
+ const noFitMessage = t("We couldn't find a size that fits for this product");
8393
+ const displaySizeLabel = sectionFound === false ? noFitMessage : selectedCountry && isRecommended && internationalSizes && internationalSizes[selectedCountry] ? internationalSizes[selectedCountry] : displaySize;
8285
8394
  const columnUnits = useMemo(() => {
8286
8395
  const units = [];
8287
8396
  for (let i = 0; i < section.headers.length; i++) {
@@ -9135,6 +9244,7 @@ function SizeResultView({
9135
9244
  const allDone = hasPhoto ? sizingDone && tryOnDone : sizingDone;
9136
9245
  const isMobile = useIsMobile();
9137
9246
  const isAccessory = measurementType === "face" || measurementType === "head";
9247
+ const noFit = sizingResult?.found === false;
9138
9248
  const vtoExcluded = measurementType === "foot";
9139
9249
  console.log("[PS-SDK] SizeResultView render:", {
9140
9250
  hasPhoto,
@@ -9219,6 +9329,7 @@ function SizeResultView({
9219
9329
  sectionName: entry.name,
9220
9330
  section: entry.section,
9221
9331
  sectionResult: entry.secResult,
9332
+ sectionFound: entry.secResult?.found,
9222
9333
  userMeasurements: entry.userMeasurements,
9223
9334
  unitLbl,
9224
9335
  chartUnit: resultUnit,
@@ -9268,6 +9379,7 @@ function SizeResultView({
9268
9379
  sectionName: entry.name,
9269
9380
  section: entry.section,
9270
9381
  sectionResult: entry.secResult,
9382
+ sectionFound: entry.secResult?.found,
9271
9383
  userMeasurements: entry.userMeasurements,
9272
9384
  unitLbl,
9273
9385
  chartUnit: resultUnit,
@@ -9389,7 +9501,7 @@ function SizeResultView({
9389
9501
  return /* @__PURE__ */ jsxs("button", { className: `ps-tryon-sr-card-v2${isLast ? " ps-full" : ""}`, onClick: () => setActiveSection(name), style: { animationDelay: `${idx * 0.07}s` }, children: [
9390
9502
  /* @__PURE__ */ jsxs("div", { className: "ps-tryon-sr-card-v2-text", children: [
9391
9503
  /* @__PURE__ */ jsx("span", { className: "ps-tryon-sr-card-v2-label", children: name.replace(/\s*[—–-]\s*.*/g, "") }),
9392
- /* @__PURE__ */ jsx("span", { className: "ps-tryon-sr-card-v2-value", children: sec.size || secResult.recommendedSize }),
9504
+ /* @__PURE__ */ jsx("span", { className: "ps-tryon-sr-card-v2-value", children: sec.found === false ? t("No fit") : sec.size || secResult.recommendedSize }),
9393
9505
  /* @__PURE__ */ jsx("span", { className: "ps-tryon-sr-card-v2-rec", children: t("recommended") })
9394
9506
  ] }),
9395
9507
  sectionImg && /* @__PURE__ */ jsx("img", { src: sectionImg, alt: name, className: "ps-tryon-sr-card-v2-img" }),
@@ -9413,7 +9525,7 @@ function SizeResultView({
9413
9525
  " →"
9414
9526
  ]
9415
9527
  }
9416
- ) : vtoExcluded ? /* @__PURE__ */ jsxs(
9528
+ ) : vtoExcluded || noFit ? /* @__PURE__ */ jsxs(
9417
9529
  "button",
9418
9530
  {
9419
9531
  className: "ps-tryon-v2-cta",
@@ -9466,6 +9578,7 @@ function SizeResultView({
9466
9578
  sectionName,
9467
9579
  section: singleSection,
9468
9580
  sectionResult: singleResult,
9581
+ sectionFound: sizingResult?.found,
9469
9582
  userMeasurements: singleUserMeasurements,
9470
9583
  unitLbl,
9471
9584
  chartUnit: resultUnit,
@@ -9473,7 +9586,7 @@ function SizeResultView({
9473
9586
  onBack: resultImageUrl ? onClose || (() => setView("body-profile")) : () => setView("body-profile"),
9474
9587
  backLabel: t("Back"),
9475
9588
  internationalSizes: sizingResult?.internationalSizes,
9476
- onTryOn: resultImageUrl || vtoExcluded ? void 0 : handleSingleTryOn,
9589
+ onTryOn: resultImageUrl || vtoExcluded || noFit ? void 0 : handleSingleTryOn,
9477
9590
  continueLabel: resultImageUrl ? t("Continue Shopping") : void 0,
9478
9591
  tryOnProcessing,
9479
9592
  productImage: resultImageUrl || productImage,
@@ -9513,6 +9626,7 @@ function SizeResultView({
9513
9626
  sectionName,
9514
9627
  section: singleSection,
9515
9628
  sectionResult: singleResult,
9629
+ sectionFound: sizingResult?.found,
9516
9630
  userMeasurements: singleUserMeasurements,
9517
9631
  unitLbl,
9518
9632
  chartUnit: resultUnit,
@@ -9520,7 +9634,7 @@ function SizeResultView({
9520
9634
  onBack: resultImageUrl ? onClose || (() => setView("body-profile")) : () => setView("body-profile"),
9521
9635
  backLabel: t("Back"),
9522
9636
  internationalSizes: sizingResult?.internationalSizes,
9523
- onTryOn: resultImageUrl || vtoExcluded ? void 0 : handleSingleTryOn,
9637
+ onTryOn: resultImageUrl || vtoExcluded || noFit ? void 0 : handleSingleTryOn,
9524
9638
  continueLabel: resultImageUrl ? t("Continue Shopping") : void 0,
9525
9639
  tryOnProcessing,
9526
9640
  t,
@@ -9896,7 +10010,7 @@ function UploadView({
9896
10010
  }
9897
10011
  ) });
9898
10012
  }
9899
- const RING_RADIUS = 20;
10013
+ const RING_RADIUS = 27;
9900
10014
  const RING_CIRCUMFERENCE = 2 * Math.PI * RING_RADIUS;
9901
10015
  function ProcessingView({
9902
10016
  previewUrl,
@@ -9941,12 +10055,12 @@ function ProcessingView({
9941
10055
  ] }),
9942
10056
  /* @__PURE__ */ jsxs("div", { className: "ps-tryon-progress-section", children: [
9943
10057
  /* @__PURE__ */ jsxs("div", { className: "ps-tryon-progress-ring", children: [
9944
- /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 48 48", width: "48", height: "48", "aria-hidden": "true", children: [
10058
+ /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 64 64", width: "64", height: "64", "aria-hidden": "true", children: [
9945
10059
  /* @__PURE__ */ jsx(
9946
10060
  "circle",
9947
10061
  {
9948
- cx: "24",
9949
- cy: "24",
10062
+ cx: "32",
10063
+ cy: "32",
9950
10064
  r: RING_RADIUS,
9951
10065
  className: "ps-tryon-progress-ring-track"
9952
10066
  }
@@ -9955,8 +10069,8 @@ function ProcessingView({
9955
10069
  "circle",
9956
10070
  {
9957
10071
  ref: ringCb,
9958
- cx: "24",
9959
- cy: "24",
10072
+ cx: "32",
10073
+ cy: "32",
9960
10074
  r: RING_RADIUS,
9961
10075
  className: "ps-tryon-progress-ring-fill",
9962
10076
  strokeDasharray: RING_CIRCUMFERENCE,
@@ -12526,7 +12640,7 @@ function BodyProfileView({
12526
12640
  hidePhotoOptions: hasActiveProfileWithMeasurements,
12527
12641
  onNext: hasActiveProfileWithMeasurements && onUseActiveProfile ? onUseActiveProfile : handleNext,
12528
12642
  canProceed: true,
12529
- fastPathLabel: hasActiveProfileWithMeasurements ? t("Find My Best Fit") : void 0,
12643
+ fastPathLabel: hasActiveProfileWithMeasurements ? t("Find My Best Fit") + (getUnitLabel(hUnit) ? ` (${getUnitLabel(hUnit)})` : "") : void 0,
12530
12644
  activeProfileName: hasActiveProfileWithMeasurements ? activeProfileName : null,
12531
12645
  onStartFresh,
12532
12646
  error,
@@ -12781,7 +12895,8 @@ function BodyProfileView({
12781
12895
  !(isMobile && step === "basics") && (() => {
12782
12896
  const useProfileFast = step === "basics" && hasActiveProfileWithMeasurements && !!onUseActiveProfile;
12783
12897
  const handleClick = useProfileFast ? onUseActiveProfile : handleNext;
12784
- const label = useProfileFast ? t("Find My Best Fit") : isLastStep ? t("Find My Size") : t("Next");
12898
+ const unitSuffix = getUnitLabel(hUnit) ? ` (${getUnitLabel(hUnit)})` : "";
12899
+ const label = useProfileFast ? t("Find My Best Fit") + unitSuffix : isLastStep ? t("Find My Size") + unitSuffix : t("Next");
12785
12900
  return /* @__PURE__ */ jsxs("div", { className: "ps-bp-nav", children: [
12786
12901
  step !== "basics" ? /* @__PURE__ */ jsxs("button", { className: "ps-bp-back-btn", onClick: handleBackStep, type: "button", children: [
12787
12902
  /* @__PURE__ */ jsx("span", { className: "ps-bp-back-arrow", children: "←" }),
@@ -12971,6 +13086,7 @@ function AccessorySizeView({
12971
13086
  /* @__PURE__ */ jsxs("div", { className: "ps-bpm-bottom", children: [
12972
13087
  /* @__PURE__ */ jsxs("button", { type: "button", className: "ps-bpm-next-btn", onClick: handleManualSubmit, children: [
12973
13088
  t("Find My Size"),
13089
+ getUnitLabel(sizingUnit) ? ` (${getUnitLabel(sizingUnit)})` : "",
12974
13090
  " ",
12975
13091
  /* @__PURE__ */ jsx(ArrowRightIcon, {})
12976
13092
  ] }),
@@ -13305,6 +13421,7 @@ function AccessorySizeView({
13305
13421
  /* @__PURE__ */ jsx("div", {}),
13306
13422
  /* @__PURE__ */ jsxs("button", { className: "ps-bp-next-btn", onClick: handleManualSubmit, type: "button", children: [
13307
13423
  t("Find My Size"),
13424
+ getUnitLabel(sizingUnit) ? ` (${getUnitLabel(sizingUnit)})` : "",
13308
13425
  " ",
13309
13426
  /* @__PURE__ */ jsx(ArrowRightIcon, {})
13310
13427
  ] })
@@ -13538,6 +13655,9 @@ function PrimeStyleTryonInner({
13538
13655
  const [sizingUnit, setSizingUnit] = useState(imperial ? "in" : "cm");
13539
13656
  const [heightUnit, setHeightUnit] = useState(imperial ? "in" : "cm");
13540
13657
  const [weightUnit, setWeightUnit] = useState(imperial ? "lbs" : "kg");
13658
+ useEffect(() => {
13659
+ if (detectMeasurementType(productTitle) === "foot") setSizingUnit("cm");
13660
+ }, [productTitle]);
13541
13661
  const formRef = useRef({});
13542
13662
  const [formGender, setFormGender] = useState("male");
13543
13663
  const [formKey, setFormKey] = useState(0);
@@ -13612,7 +13732,7 @@ function PrimeStyleTryonInner({
13612
13732
  { at: 75, text: t("Refining details...") },
13613
13733
  { at: 90, text: t("Almost there...") }
13614
13734
  ];
13615
- const RING_CIRCUMFERENCE2 = 2 * Math.PI * 20;
13735
+ const RING_CIRCUMFERENCE2 = 2 * Math.PI * 27;
13616
13736
  progressIntervalRef.current = setInterval(() => {
13617
13737
  if (completedRef.current) return;
13618
13738
  const startTs = progressStartTsRef.current || Date.now();
@@ -13790,13 +13910,10 @@ function PrimeStyleTryonInner({
13790
13910
  [activeProfileId, profiles, apiUrl, productImage, productTitle, effectiveProductId, setActiveProfileId$1]
13791
13911
  );
13792
13912
  const snapSubmitRef = useRef(null);
13793
- const handleUseActiveProfile = useCallback(async () => {
13794
- const p = profiles.find((x) => x.id === activeProfileId);
13795
- if (!p) return;
13913
+ const [confirmProfile, setConfirmProfile] = useState(null);
13914
+ const runRecommendWithProfile = useCallback(async (p) => {
13796
13915
  const profileHeight = p.height ?? p.heightCm ?? 0;
13797
13916
  const profileWeight = p.weight ?? p.weightKg ?? 0;
13798
- const hasIdentity = profileHeight > 0 && profileWeight > 0;
13799
- if (!hasIdentity) return;
13800
13917
  const hasStored = !!p.measurements && Object.keys(p.measurements).length > 0;
13801
13918
  const storedPhoto = p.photoBase64;
13802
13919
  if (!hasStored && storedPhoto && profileHeight > 0 && snapSubmitRef.current) {
@@ -13820,9 +13937,8 @@ function PrimeStyleTryonInner({
13820
13937
  }
13821
13938
  setSizingResult(null);
13822
13939
  setSizingLoading(true);
13823
- const hasStoredMeasurements = !!p.measurements && Object.keys(p.measurements).length > 0;
13824
- setEstimationDone(hasStoredMeasurements);
13825
- if (hasStoredMeasurements) {
13940
+ setEstimationDone(hasStored);
13941
+ if (hasStored) {
13826
13942
  setPreviewUrl(null);
13827
13943
  setBodyLandmarks(null);
13828
13944
  }
@@ -13855,7 +13971,27 @@ function PrimeStyleTryonInner({
13855
13971
  setEstimationDone(true);
13856
13972
  }).catch(() => {
13857
13973
  }).finally(() => setSizingLoading(false));
13858
- }, [profiles, activeProfileId, effectiveProductId, productTitle, productImage, sizeGuideData, apiUrl]);
13974
+ }, [effectiveProductId, productTitle, productImage, sizeGuideData, apiUrl, previewUrl]);
13975
+ const handleUseActiveProfile = useCallback(async () => {
13976
+ const p = profiles.find((x) => x.id === activeProfileId);
13977
+ if (!p) return;
13978
+ const profileHeight = p.height ?? p.heightCm ?? 0;
13979
+ const profileWeight = p.weight ?? p.weightKg ?? 0;
13980
+ const hasIdentity = profileHeight > 0 && profileWeight > 0;
13981
+ if (!hasIdentity) return;
13982
+ setConfirmProfile(p);
13983
+ }, [profiles, activeProfileId]);
13984
+ const proceedFromConfirmProfile = useCallback(() => {
13985
+ if (!confirmProfile) return;
13986
+ const p = confirmProfile;
13987
+ setConfirmProfile(null);
13988
+ void runRecommendWithProfile(p);
13989
+ }, [confirmProfile, runRecommendWithProfile]);
13990
+ const cancelFromConfirmProfile = useCallback(() => {
13991
+ setConfirmProfile(null);
13992
+ setFormKey((k) => k + 1);
13993
+ setView("body-profile");
13994
+ }, []);
13859
13995
  const applyProfileRef = useRef(() => {
13860
13996
  });
13861
13997
  const handleOpen = useCallback(() => {
@@ -15230,6 +15366,15 @@ function PrimeStyleTryonInner({
15230
15366
  onCancel: () => setDeleteConfirmId(null),
15231
15367
  t
15232
15368
  }
15369
+ ),
15370
+ confirmProfile && /* @__PURE__ */ jsx(
15371
+ ConfirmMeasurementsModal,
15372
+ {
15373
+ profile: confirmProfile,
15374
+ onProceed: proceedFromConfirmProfile,
15375
+ onEdit: cancelFromConfirmProfile,
15376
+ t
15377
+ }
15233
15378
  )
15234
15379
  ] }) }),
15235
15380
  document.body
@@ -27,6 +27,10 @@ export interface RecommendForProductResult {
27
27
  fromCache: boolean;
28
28
  /** Full sizing result for downstream use */
29
29
  raw?: SizingResult;
30
+ /** True when the backend found a real fit. False when the user's body is
31
+ * outside every row in the size chart — hosts should suppress their
32
+ * "Recommended: X" badge and surface a "size unavailable" message. */
33
+ found?: boolean;
30
34
  }
31
35
  /**
32
36
  * Headless auto-sizing — given a product and an active profile, returns the