@primestyleai/tryon 5.9.1 → 5.10.1

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
  }
@@ -833,12 +840,15 @@ const STYLES = `
833
840
  .ps-tryon-logo-img { height: var(--ps-logo-height); width: auto; }
834
841
  .ps-tryon-header-actions { display: flex; align-items: center; gap: 0.42vw; }
835
842
  .ps-tryon-header-icon {
836
- width: 2.2vw; height: 2.2vw; display: flex; align-items: center; justify-content: center;
837
- border: 1.5px solid var(--ps-border-color); border-radius: 0.52vw; background: transparent;
843
+ /* Pure vw sizing collapsed to ~8 px on 375 px mobile. Clamp so the icon
844
+ stays finger-tappable (min 30 px) while scaling up on large screens. */
845
+ width: clamp(30px, 2.2vw, 34px); height: clamp(30px, 2.2vw, 34px);
846
+ display: flex; align-items: center; justify-content: center;
847
+ border: 1.5px solid var(--ps-border-color); border-radius: clamp(6px, 0.52vw, 10px); background: transparent;
838
848
  cursor: pointer; color: var(--ps-text-secondary); transition: all 0.2s;
839
849
  }
840
850
  .ps-tryon-header-icon:hover { border-color: var(--ps-accent); color: var(--ps-accent); }
841
- .ps-tryon-header-icon svg { stroke: currentColor; fill: none; width: 0.9vw; height: 0.9vw; }
851
+ .ps-tryon-header-icon svg { stroke: currentColor; fill: none; width: clamp(14px, 0.9vw, 16px); height: clamp(14px, 0.9vw, 16px); }
842
852
  .ps-tryon-close {
843
853
  width: 2.2vw; height: 2.2vw; display: flex; align-items: center; justify-content: center;
844
854
  background: none; border: none; color: var(--ps-modal-close-color, #999);
@@ -886,7 +896,10 @@ const STYLES = `
886
896
  }
887
897
 
888
898
  .ps-tryon-lang-list {
889
- max-height: min(18vw, 280px); overflow-y: auto; padding: clamp(3px, 0.31vw, 5px);
899
+ /* max(...) picks the larger of the two values so the dropdown is tall
900
+ enough to scroll through a handful of languages on any viewport.
901
+ Pure min(18vw, 280px) collapsed to ~67 px on 375 px mobile. */
902
+ max-height: max(260px, min(18vw, 280px)); overflow-y: auto; padding: clamp(3px, 0.31vw, 5px);
890
903
  scrollbar-width: thin; scrollbar-color: rgba(0,0,0,0.15) transparent;
891
904
  }
892
905
  .ps-tryon-lang-item {
@@ -2099,21 +2112,21 @@ const STYLES = `
2099
2112
  /* Shared progress layout used inside StageCycler (desktop) and
2100
2113
  MobileScanningView — row of ring + bar + percent, same tokens. */
2101
2114
  .ps-tryon-progress-wrap {
2102
- display: flex; align-items: center; gap: 10px;
2103
- width: 100%; max-width: 320px; margin-top: 16px;
2115
+ display: flex; align-items: center; gap: 12px;
2116
+ width: 100%; max-width: 360px; margin-top: 18px;
2104
2117
  }
2105
2118
  .ps-tryon-progress-wrap .ps-tryon-progress-bar-wrap {
2106
- flex: 1; height: 4px; border-radius: 3px; overflow: hidden;
2119
+ flex: 1; height: 6px; border-radius: 4px; overflow: hidden;
2107
2120
  position: relative; background: var(--ps-border-color);
2108
2121
  }
2109
2122
  .ps-tryon-progress-wrap .ps-tryon-progress-bar-fill {
2110
2123
  height: 100%; width: 0%;
2111
2124
  background: linear-gradient(90deg, var(--ps-accent), var(--ps-accent-light));
2112
- border-radius: 3px; transition: width 0.3s ease;
2125
+ border-radius: 4px; transition: width 0.3s ease;
2113
2126
  }
2114
2127
  .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;
2128
+ font-size: 13px; font-weight: 700; color: var(--ps-accent);
2129
+ min-width: 36px; text-align: right;
2117
2130
  font-variant-numeric: tabular-nums;
2118
2131
  }
2119
2132
  .ps-tryon-progress-bar-wrap {
@@ -2141,25 +2154,25 @@ const STYLES = `
2141
2154
  font-variant-numeric: tabular-nums;
2142
2155
  }
2143
2156
 
2144
- /* Circular ETA ring — 48×48 px SVG with a track + progress circle; ETA
2157
+ /* Circular ETA ring — 64×64 px SVG with a track + progress circle; ETA
2145
2158
  text centered. strokeDashoffset is driven by the ticker in
2146
2159
  PrimeStyleTryonInner, so CSS only styles the appearance. */
2147
2160
  .ps-tryon-progress-ring {
2148
- position: relative; width: 48px; height: 48px; flex: 0 0 48px;
2161
+ position: relative; width: 64px; height: 64px; flex: 0 0 64px;
2149
2162
  display: flex; align-items: center; justify-content: center;
2150
2163
  }
2151
2164
  .ps-tryon-progress-ring svg { transform: rotate(-90deg); }
2152
2165
  .ps-tryon-progress-ring-track {
2153
- fill: none; stroke: var(--ps-border-color); stroke-width: 3.5;
2166
+ fill: none; stroke: var(--ps-border-color); stroke-width: 5;
2154
2167
  }
2155
2168
  .ps-tryon-progress-ring-fill {
2156
- fill: none; stroke: var(--ps-accent); stroke-width: 3.5;
2169
+ fill: none; stroke: var(--ps-accent); stroke-width: 5;
2157
2170
  stroke-linecap: round;
2158
2171
  transition: stroke-dashoffset 0.3s ease;
2159
2172
  }
2160
2173
  .ps-tryon-progress-eta {
2161
2174
  position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
2162
- font-size: 10px; font-weight: 700; color: var(--ps-accent);
2175
+ font-size: 13px; font-weight: 700; color: var(--ps-accent);
2163
2176
  font-variant-numeric: tabular-nums; letter-spacing: 0.01em;
2164
2177
  pointer-events: none;
2165
2178
  }
@@ -3279,6 +3292,33 @@ const STYLES = `
3279
3292
 
3280
3293
  /* ── Preserve existing previews and modal-wide overrides ── */
3281
3294
  .ps-tryon-preview { height: 320px; }
3295
+
3296
+ /* ── Drawer list (history / profiles / settings) ── */
3297
+ /* Pure vw gap (0.52vw = ~2 px on mobile) crushed list rows together. */
3298
+ .ps-tryon-drawer-list { gap: 12px !important; padding: 0 !important; }
3299
+ .ps-tryon-drawer { padding: 16px !important; }
3300
+
3301
+ /* ── Profile cards inside the sizing-profiles drawer ── */
3302
+ .ps-msp-card {
3303
+ padding: 16px !important;
3304
+ border-radius: 14px !important;
3305
+ }
3306
+ .ps-msp-card-tag { font-size: 11px !important; padding: 4px 8px !important; border-radius: 999px !important; }
3307
+ .ps-msp-card-circle { width: 72px !important; height: 72px !important; margin: 8px auto 12px !important; }
3308
+ .ps-msp-card-name { font-size: 16px !important; margin-bottom: 6px !important; }
3309
+ .ps-msp-meta-row { padding: 6px 0 !important; gap: 8px !important; }
3310
+ .ps-msp-card-meta { font-size: 12px !important; }
3311
+ .ps-msp-card-actions { gap: 8px !important; margin-top: 10px !important; }
3312
+ .ps-msp-card-select { font-size: 13px !important; padding: 10px 12px !important; border-radius: 8px !important; }
3313
+ .ps-msp-card-edit, .ps-msp-card-delete { width: 36px !important; height: 36px !important; border-radius: 8px !important; }
3314
+ .ps-msp-card-create { min-height: 120px !important; font-size: 14px !important; }
3315
+
3316
+ /* ── Language switcher dropdown ── */
3317
+ /* Default min(18vw, 280px) collapsed to ~67 px on mobile — unusable. */
3318
+ .ps-tryon-lang-list { max-height: 320px !important; }
3319
+ .ps-tryon-lang-item { padding: 10px 14px !important; gap: 10px !important; }
3320
+ .ps-tryon-lang-name { font-size: 14px !important; }
3321
+ .ps-tryon-lang-code { font-size: 11px !important; }
3282
3322
  }
3283
3323
 
3284
3324
  @keyframes ps-mobile-slide-up {
@@ -7246,6 +7286,92 @@ function ProfileDetailModal({
7246
7286
  document.body
7247
7287
  );
7248
7288
  }
7289
+ function ConfirmMeasurementsModal({
7290
+ profile,
7291
+ onProceed,
7292
+ onEdit,
7293
+ t
7294
+ }) {
7295
+ const heightUnit = profile.heightUnit === "in" || profile.heightUnit === "ft" ? "in" : "cm";
7296
+ const weightUnit = profile.weightUnit === "lbs" ? "lbs" : "kg";
7297
+ const systemLabel = getUnitLabel(heightUnit);
7298
+ const formatHeight = (h) => {
7299
+ if (!h) return "—";
7300
+ if (heightUnit === "in") {
7301
+ const ft = Math.floor(h / 12);
7302
+ const inches = Math.round(h % 12);
7303
+ return `${ft}'${inches}"`;
7304
+ }
7305
+ return `${Math.round(h)} cm`;
7306
+ };
7307
+ const formatWeight = (w) => {
7308
+ if (!w) return "—";
7309
+ return `${Math.round(w)} ${weightUnit}`;
7310
+ };
7311
+ const height = profile.height ?? profile.heightCm;
7312
+ const weight = profile.weight ?? profile.weightKg;
7313
+ return /* @__PURE__ */ jsx("div", { className: "ps-confirm-overlay", onClick: onEdit, children: /* @__PURE__ */ jsxs("div", { className: "ps-confirm-modal", onClick: (e) => e.stopPropagation(), children: [
7314
+ /* @__PURE__ */ jsx(
7315
+ "button",
7316
+ {
7317
+ type: "button",
7318
+ "aria-label": t("Close"),
7319
+ onClick: onEdit,
7320
+ style: {
7321
+ position: "absolute",
7322
+ top: "0.75vw",
7323
+ right: "0.75vw",
7324
+ width: "1.8vw",
7325
+ height: "1.8vw",
7326
+ borderRadius: "50%",
7327
+ background: "transparent",
7328
+ border: "none",
7329
+ cursor: "pointer",
7330
+ display: "flex",
7331
+ alignItems: "center",
7332
+ justifyContent: "center",
7333
+ color: "var(--ps-text-muted)"
7334
+ },
7335
+ children: /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", width: "16", height: "16", children: [
7336
+ /* @__PURE__ */ jsx("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
7337
+ /* @__PURE__ */ jsx("line", { x1: "6", y1: "6", x2: "18", y2: "18" })
7338
+ ] })
7339
+ }
7340
+ ),
7341
+ /* @__PURE__ */ jsx("p", { style: { fontWeight: 700, marginBottom: "0.4vw" }, children: t("Confirm your measurements") }),
7342
+ /* @__PURE__ */ jsx("small", { style: { color: "var(--ps-text-muted)" }, children: systemLabel ? t("You chose") + " " + systemLabel + ". " + t("Review before continuing.") : t("Review before continuing.") }),
7343
+ /* @__PURE__ */ jsxs("ul", { style: {
7344
+ listStyle: "none",
7345
+ padding: 0,
7346
+ margin: "0.8vw 0 0.2vw 0",
7347
+ width: "100%",
7348
+ textAlign: "left",
7349
+ fontSize: "0.78vw",
7350
+ lineHeight: 1.6
7351
+ }, children: [
7352
+ /* @__PURE__ */ jsxs("li", { style: { display: "flex", justifyContent: "space-between", gap: "1vw" }, children: [
7353
+ /* @__PURE__ */ jsx("span", { style: { color: "var(--ps-text-muted)" }, children: t("Height") }),
7354
+ /* @__PURE__ */ jsx("span", { style: { fontWeight: 600 }, children: formatHeight(height) })
7355
+ ] }),
7356
+ /* @__PURE__ */ jsxs("li", { style: { display: "flex", justifyContent: "space-between", gap: "1vw" }, children: [
7357
+ /* @__PURE__ */ jsx("span", { style: { color: "var(--ps-text-muted)" }, children: t("Weight") }),
7358
+ /* @__PURE__ */ jsx("span", { style: { fontWeight: 600 }, children: formatWeight(weight) })
7359
+ ] }),
7360
+ profile.age ? /* @__PURE__ */ jsxs("li", { style: { display: "flex", justifyContent: "space-between", gap: "1vw" }, children: [
7361
+ /* @__PURE__ */ jsx("span", { style: { color: "var(--ps-text-muted)" }, children: t("Age") }),
7362
+ /* @__PURE__ */ jsx("span", { style: { fontWeight: 600 }, children: profile.age })
7363
+ ] }) : null,
7364
+ /* @__PURE__ */ jsxs("li", { style: { display: "flex", justifyContent: "space-between", gap: "1vw" }, children: [
7365
+ /* @__PURE__ */ jsx("span", { style: { color: "var(--ps-text-muted)" }, children: t("Gender") }),
7366
+ /* @__PURE__ */ jsx("span", { style: { fontWeight: 600 }, children: profile.gender === "female" ? t("Female") : t("Male") })
7367
+ ] })
7368
+ ] }),
7369
+ /* @__PURE__ */ jsxs("div", { className: "ps-confirm-actions", children: [
7370
+ /* @__PURE__ */ jsx("button", { type: "button", className: "ps-confirm-cancel", onClick: onEdit, children: t("Edit") }),
7371
+ /* @__PURE__ */ jsx("button", { type: "button", className: "ps-confirm-delete", onClick: onProceed, children: t("Proceed") })
7372
+ ] })
7373
+ ] }) });
7374
+ }
7249
7375
  function WelcomeView({
7250
7376
  productImage,
7251
7377
  setView,
@@ -7482,7 +7608,7 @@ function MobileSkeleton({ landmarks, w, h }) {
7482
7608
  );
7483
7609
  }
7484
7610
  const MSC_TRYON_TARGET_SECONDS = 22;
7485
- const MSC_RING_RADIUS = 20;
7611
+ const MSC_RING_RADIUS = 27;
7486
7612
  const MSC_RING_CIRC = 2 * Math.PI * MSC_RING_RADIUS;
7487
7613
  function MscTryOnProgress({ t }) {
7488
7614
  const startRef = useRef(Date.now());
@@ -7505,17 +7631,17 @@ function MscTryOnProgress({ t }) {
7505
7631
  }
7506
7632
  }, 200);
7507
7633
  return () => clearInterval(id);
7508
- }, [t]);
7634
+ }, []);
7509
7635
  return /* @__PURE__ */ jsxs("div", { className: "ps-tryon-progress-wrap", children: [
7510
7636
  /* @__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" }),
7637
+ /* @__PURE__ */ jsxs("svg", { width: "64", height: "64", viewBox: "0 0 64 64", "aria-hidden": "true", children: [
7638
+ /* @__PURE__ */ jsx("circle", { cx: "32", cy: "32", r: MSC_RING_RADIUS, className: "ps-tryon-progress-ring-track" }),
7513
7639
  /* @__PURE__ */ jsx(
7514
7640
  "circle",
7515
7641
  {
7516
7642
  ref: ringRef,
7517
- cx: "24",
7518
- cy: "24",
7643
+ cx: "32",
7644
+ cy: "32",
7519
7645
  r: MSC_RING_RADIUS,
7520
7646
  className: "ps-tryon-progress-ring-fill",
7521
7647
  strokeDasharray: MSC_RING_CIRC,
@@ -7564,11 +7690,12 @@ function MobileScanningView({
7564
7690
  };
7565
7691
  const [stageIdx, setStageIdx] = useState(0);
7566
7692
  useEffect(() => {
7693
+ const intervalMs = tryOnProcessing ? 2200 : 1500;
7567
7694
  const id = setInterval(() => {
7568
7695
  setStageIdx((i) => (i + 1) % stages.length);
7569
- }, 1500);
7696
+ }, intervalMs);
7570
7697
  return () => clearInterval(id);
7571
- }, [stages.length]);
7698
+ }, [stages.length, tryOnProcessing]);
7572
7699
  useEffect(() => {
7573
7700
  if (isPhotoMode && bodyLandmarks && stageIdx === 0) {
7574
7701
  setStageIdx(1);
@@ -7725,7 +7852,7 @@ function MultiSectionMobile({
7725
7852
  /* @__PURE__ */ jsx("div", { className: "ps-msr-sections", children: sectionEntries.map(({ name, secResult }) => {
7726
7853
  const cleanName = name.replace(/\s*[—–-]\s*.*/g, "");
7727
7854
  const sec = secResult;
7728
- const sizeValue = sec.size || secResult.recommendedSize;
7855
+ const sizeValue = sec.found === false ? t("No fit") : sec.size || secResult.recommendedSize;
7729
7856
  return /* @__PURE__ */ jsxs(
7730
7857
  "button",
7731
7858
  {
@@ -7760,7 +7887,19 @@ function MultiSectionMobile({
7760
7887
  children: t("Continue Shopping")
7761
7888
  }
7762
7889
  )
7763
- ] }) : /* @__PURE__ */ jsxs(
7890
+ ] }) : sizingResult?.found === false ? (
7891
+ // Backend couldn't find a size that fits — Try-On is meaningless
7892
+ // without a recommendation, so surface a clear terminal action.
7893
+ /* @__PURE__ */ jsx(
7894
+ "button",
7895
+ {
7896
+ type: "button",
7897
+ className: "ps-msr-tryon-cta",
7898
+ onClick: onClose,
7899
+ children: t("Continue Shopping")
7900
+ }
7901
+ )
7902
+ ) : /* @__PURE__ */ jsxs(
7764
7903
  "button",
7765
7904
  {
7766
7905
  type: "button",
@@ -7793,7 +7932,7 @@ const SKELETON_CONNECTIONS = [
7793
7932
  ["rightKnee", "rightAnkle"]
7794
7933
  ];
7795
7934
  const TRYON_TARGET_SECONDS = 22;
7796
- const TRYON_RING_RADIUS = 20;
7935
+ const TRYON_RING_RADIUS = 27;
7797
7936
  const TRYON_RING_CIRC = 2 * Math.PI * TRYON_RING_RADIUS;
7798
7937
  function TryOnProgress({ t, isActive }) {
7799
7938
  const startRef = useRef(null);
@@ -7823,18 +7962,18 @@ function TryOnProgress({ t, isActive }) {
7823
7962
  }
7824
7963
  }, 200);
7825
7964
  return () => clearInterval(id);
7826
- }, [isActive, t]);
7965
+ }, [isActive]);
7827
7966
  if (!isActive) return null;
7828
7967
  return /* @__PURE__ */ jsxs("div", { className: "ps-tryon-progress-wrap", children: [
7829
7968
  /* @__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" }),
7969
+ /* @__PURE__ */ jsxs("svg", { width: "64", height: "64", viewBox: "0 0 64 64", "aria-hidden": "true", children: [
7970
+ /* @__PURE__ */ jsx("circle", { cx: "32", cy: "32", r: TRYON_RING_RADIUS, className: "ps-tryon-progress-ring-track" }),
7832
7971
  /* @__PURE__ */ jsx(
7833
7972
  "circle",
7834
7973
  {
7835
7974
  ref: ringRef,
7836
- cx: "24",
7837
- cy: "24",
7975
+ cx: "32",
7976
+ cy: "32",
7838
7977
  r: TRYON_RING_RADIUS,
7839
7978
  className: "ps-tryon-progress-ring-fill",
7840
7979
  strokeDasharray: TRYON_RING_CIRC,
@@ -7985,11 +8124,12 @@ function StageCycler({
7985
8124
  }, [tryOnProcessing]);
7986
8125
  useEffect(() => {
7987
8126
  if (isDone) return;
8127
+ const intervalMs = tryOnProcessing ? 2200 : 900;
7988
8128
  const id = setInterval(() => {
7989
8129
  setIdx((i) => Math.min(i + 1, active.length - 1));
7990
- }, 900);
8130
+ }, intervalMs);
7991
8131
  return () => clearInterval(id);
7992
- }, [isDone, active.length]);
8132
+ }, [isDone, active.length, tryOnProcessing]);
7993
8133
  const current = active[idx] ?? active[0];
7994
8134
  return /* @__PURE__ */ jsxs("div", { className: "ps-msc-stage", style: { alignSelf: "center", marginTop: "auto", marginBottom: "auto" }, children: [
7995
8135
  /* @__PURE__ */ jsxs("div", { className: "ps-msc-stage-slot", children: [
@@ -8236,7 +8376,8 @@ function SectionDetailView({
8236
8376
  backLabel,
8237
8377
  internationalSizes,
8238
8378
  continueLabel,
8239
- renderRaw = false
8379
+ renderRaw = false,
8380
+ sectionFound
8240
8381
  }) {
8241
8382
  const recSize = sectionResult?.recommendedSize || "";
8242
8383
  const [selectedSize, setSelectedSize] = useState(null);
@@ -8281,7 +8422,8 @@ function SectionDetailView({
8281
8422
  const hasBadFit = details.some((d) => BAD_FIT.test(d.fit || ""));
8282
8423
  return hasBadFit ? t("Not Recommended") : t("Your Selection");
8283
8424
  }, [isRecommended, sectionResult, t]);
8284
- const displaySizeLabel = selectedCountry && isRecommended && internationalSizes && internationalSizes[selectedCountry] ? internationalSizes[selectedCountry] : displaySize;
8425
+ const noFitMessage = t("We couldn't find a size that fits for this product");
8426
+ const displaySizeLabel = sectionFound === false ? noFitMessage : selectedCountry && isRecommended && internationalSizes && internationalSizes[selectedCountry] ? internationalSizes[selectedCountry] : displaySize;
8285
8427
  const columnUnits = useMemo(() => {
8286
8428
  const units = [];
8287
8429
  for (let i = 0; i < section.headers.length; i++) {
@@ -9135,6 +9277,7 @@ function SizeResultView({
9135
9277
  const allDone = hasPhoto ? sizingDone && tryOnDone : sizingDone;
9136
9278
  const isMobile = useIsMobile();
9137
9279
  const isAccessory = measurementType === "face" || measurementType === "head";
9280
+ const noFit = sizingResult?.found === false;
9138
9281
  const vtoExcluded = measurementType === "foot";
9139
9282
  console.log("[PS-SDK] SizeResultView render:", {
9140
9283
  hasPhoto,
@@ -9219,6 +9362,7 @@ function SizeResultView({
9219
9362
  sectionName: entry.name,
9220
9363
  section: entry.section,
9221
9364
  sectionResult: entry.secResult,
9365
+ sectionFound: entry.secResult?.found,
9222
9366
  userMeasurements: entry.userMeasurements,
9223
9367
  unitLbl,
9224
9368
  chartUnit: resultUnit,
@@ -9268,6 +9412,7 @@ function SizeResultView({
9268
9412
  sectionName: entry.name,
9269
9413
  section: entry.section,
9270
9414
  sectionResult: entry.secResult,
9415
+ sectionFound: entry.secResult?.found,
9271
9416
  userMeasurements: entry.userMeasurements,
9272
9417
  unitLbl,
9273
9418
  chartUnit: resultUnit,
@@ -9389,7 +9534,7 @@ function SizeResultView({
9389
9534
  return /* @__PURE__ */ jsxs("button", { className: `ps-tryon-sr-card-v2${isLast ? " ps-full" : ""}`, onClick: () => setActiveSection(name), style: { animationDelay: `${idx * 0.07}s` }, children: [
9390
9535
  /* @__PURE__ */ jsxs("div", { className: "ps-tryon-sr-card-v2-text", children: [
9391
9536
  /* @__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 }),
9537
+ /* @__PURE__ */ jsx("span", { className: "ps-tryon-sr-card-v2-value", children: sec.found === false ? t("No fit") : sec.size || secResult.recommendedSize }),
9393
9538
  /* @__PURE__ */ jsx("span", { className: "ps-tryon-sr-card-v2-rec", children: t("recommended") })
9394
9539
  ] }),
9395
9540
  sectionImg && /* @__PURE__ */ jsx("img", { src: sectionImg, alt: name, className: "ps-tryon-sr-card-v2-img" }),
@@ -9413,7 +9558,7 @@ function SizeResultView({
9413
9558
  " →"
9414
9559
  ]
9415
9560
  }
9416
- ) : vtoExcluded ? /* @__PURE__ */ jsxs(
9561
+ ) : vtoExcluded || noFit ? /* @__PURE__ */ jsxs(
9417
9562
  "button",
9418
9563
  {
9419
9564
  className: "ps-tryon-v2-cta",
@@ -9466,6 +9611,7 @@ function SizeResultView({
9466
9611
  sectionName,
9467
9612
  section: singleSection,
9468
9613
  sectionResult: singleResult,
9614
+ sectionFound: sizingResult?.found,
9469
9615
  userMeasurements: singleUserMeasurements,
9470
9616
  unitLbl,
9471
9617
  chartUnit: resultUnit,
@@ -9473,7 +9619,7 @@ function SizeResultView({
9473
9619
  onBack: resultImageUrl ? onClose || (() => setView("body-profile")) : () => setView("body-profile"),
9474
9620
  backLabel: t("Back"),
9475
9621
  internationalSizes: sizingResult?.internationalSizes,
9476
- onTryOn: resultImageUrl || vtoExcluded ? void 0 : handleSingleTryOn,
9622
+ onTryOn: resultImageUrl || vtoExcluded || noFit ? void 0 : handleSingleTryOn,
9477
9623
  continueLabel: resultImageUrl ? t("Continue Shopping") : void 0,
9478
9624
  tryOnProcessing,
9479
9625
  productImage: resultImageUrl || productImage,
@@ -9513,6 +9659,7 @@ function SizeResultView({
9513
9659
  sectionName,
9514
9660
  section: singleSection,
9515
9661
  sectionResult: singleResult,
9662
+ sectionFound: sizingResult?.found,
9516
9663
  userMeasurements: singleUserMeasurements,
9517
9664
  unitLbl,
9518
9665
  chartUnit: resultUnit,
@@ -9520,7 +9667,7 @@ function SizeResultView({
9520
9667
  onBack: resultImageUrl ? onClose || (() => setView("body-profile")) : () => setView("body-profile"),
9521
9668
  backLabel: t("Back"),
9522
9669
  internationalSizes: sizingResult?.internationalSizes,
9523
- onTryOn: resultImageUrl || vtoExcluded ? void 0 : handleSingleTryOn,
9670
+ onTryOn: resultImageUrl || vtoExcluded || noFit ? void 0 : handleSingleTryOn,
9524
9671
  continueLabel: resultImageUrl ? t("Continue Shopping") : void 0,
9525
9672
  tryOnProcessing,
9526
9673
  t,
@@ -9896,7 +10043,7 @@ function UploadView({
9896
10043
  }
9897
10044
  ) });
9898
10045
  }
9899
- const RING_RADIUS = 20;
10046
+ const RING_RADIUS = 27;
9900
10047
  const RING_CIRCUMFERENCE = 2 * Math.PI * RING_RADIUS;
9901
10048
  function ProcessingView({
9902
10049
  previewUrl,
@@ -9941,12 +10088,12 @@ function ProcessingView({
9941
10088
  ] }),
9942
10089
  /* @__PURE__ */ jsxs("div", { className: "ps-tryon-progress-section", children: [
9943
10090
  /* @__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: [
10091
+ /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 64 64", width: "64", height: "64", "aria-hidden": "true", children: [
9945
10092
  /* @__PURE__ */ jsx(
9946
10093
  "circle",
9947
10094
  {
9948
- cx: "24",
9949
- cy: "24",
10095
+ cx: "32",
10096
+ cy: "32",
9950
10097
  r: RING_RADIUS,
9951
10098
  className: "ps-tryon-progress-ring-track"
9952
10099
  }
@@ -9955,8 +10102,8 @@ function ProcessingView({
9955
10102
  "circle",
9956
10103
  {
9957
10104
  ref: ringCb,
9958
- cx: "24",
9959
- cy: "24",
10105
+ cx: "32",
10106
+ cy: "32",
9960
10107
  r: RING_RADIUS,
9961
10108
  className: "ps-tryon-progress-ring-fill",
9962
10109
  strokeDasharray: RING_CIRCUMFERENCE,
@@ -12526,7 +12673,7 @@ function BodyProfileView({
12526
12673
  hidePhotoOptions: hasActiveProfileWithMeasurements,
12527
12674
  onNext: hasActiveProfileWithMeasurements && onUseActiveProfile ? onUseActiveProfile : handleNext,
12528
12675
  canProceed: true,
12529
- fastPathLabel: hasActiveProfileWithMeasurements ? t("Find My Best Fit") : void 0,
12676
+ fastPathLabel: hasActiveProfileWithMeasurements ? t("Find My Best Fit") + (getUnitLabel(hUnit) ? ` (${getUnitLabel(hUnit)})` : "") : void 0,
12530
12677
  activeProfileName: hasActiveProfileWithMeasurements ? activeProfileName : null,
12531
12678
  onStartFresh,
12532
12679
  error,
@@ -12781,7 +12928,8 @@ function BodyProfileView({
12781
12928
  !(isMobile && step === "basics") && (() => {
12782
12929
  const useProfileFast = step === "basics" && hasActiveProfileWithMeasurements && !!onUseActiveProfile;
12783
12930
  const handleClick = useProfileFast ? onUseActiveProfile : handleNext;
12784
- const label = useProfileFast ? t("Find My Best Fit") : isLastStep ? t("Find My Size") : t("Next");
12931
+ const unitSuffix = getUnitLabel(hUnit) ? ` (${getUnitLabel(hUnit)})` : "";
12932
+ const label = useProfileFast ? t("Find My Best Fit") + unitSuffix : isLastStep ? t("Find My Size") + unitSuffix : t("Next");
12785
12933
  return /* @__PURE__ */ jsxs("div", { className: "ps-bp-nav", children: [
12786
12934
  step !== "basics" ? /* @__PURE__ */ jsxs("button", { className: "ps-bp-back-btn", onClick: handleBackStep, type: "button", children: [
12787
12935
  /* @__PURE__ */ jsx("span", { className: "ps-bp-back-arrow", children: "←" }),
@@ -12971,6 +13119,7 @@ function AccessorySizeView({
12971
13119
  /* @__PURE__ */ jsxs("div", { className: "ps-bpm-bottom", children: [
12972
13120
  /* @__PURE__ */ jsxs("button", { type: "button", className: "ps-bpm-next-btn", onClick: handleManualSubmit, children: [
12973
13121
  t("Find My Size"),
13122
+ getUnitLabel(sizingUnit) ? ` (${getUnitLabel(sizingUnit)})` : "",
12974
13123
  " ",
12975
13124
  /* @__PURE__ */ jsx(ArrowRightIcon, {})
12976
13125
  ] }),
@@ -13305,6 +13454,7 @@ function AccessorySizeView({
13305
13454
  /* @__PURE__ */ jsx("div", {}),
13306
13455
  /* @__PURE__ */ jsxs("button", { className: "ps-bp-next-btn", onClick: handleManualSubmit, type: "button", children: [
13307
13456
  t("Find My Size"),
13457
+ getUnitLabel(sizingUnit) ? ` (${getUnitLabel(sizingUnit)})` : "",
13308
13458
  " ",
13309
13459
  /* @__PURE__ */ jsx(ArrowRightIcon, {})
13310
13460
  ] })
@@ -13538,6 +13688,9 @@ function PrimeStyleTryonInner({
13538
13688
  const [sizingUnit, setSizingUnit] = useState(imperial ? "in" : "cm");
13539
13689
  const [heightUnit, setHeightUnit] = useState(imperial ? "in" : "cm");
13540
13690
  const [weightUnit, setWeightUnit] = useState(imperial ? "lbs" : "kg");
13691
+ useEffect(() => {
13692
+ if (detectMeasurementType(productTitle) === "foot") setSizingUnit("cm");
13693
+ }, [productTitle]);
13541
13694
  const formRef = useRef({});
13542
13695
  const [formGender, setFormGender] = useState("male");
13543
13696
  const [formKey, setFormKey] = useState(0);
@@ -13612,7 +13765,7 @@ function PrimeStyleTryonInner({
13612
13765
  { at: 75, text: t("Refining details...") },
13613
13766
  { at: 90, text: t("Almost there...") }
13614
13767
  ];
13615
- const RING_CIRCUMFERENCE2 = 2 * Math.PI * 20;
13768
+ const RING_CIRCUMFERENCE2 = 2 * Math.PI * 27;
13616
13769
  progressIntervalRef.current = setInterval(() => {
13617
13770
  if (completedRef.current) return;
13618
13771
  const startTs = progressStartTsRef.current || Date.now();
@@ -13790,13 +13943,10 @@ function PrimeStyleTryonInner({
13790
13943
  [activeProfileId, profiles, apiUrl, productImage, productTitle, effectiveProductId, setActiveProfileId$1]
13791
13944
  );
13792
13945
  const snapSubmitRef = useRef(null);
13793
- const handleUseActiveProfile = useCallback(async () => {
13794
- const p = profiles.find((x) => x.id === activeProfileId);
13795
- if (!p) return;
13946
+ const [confirmProfile, setConfirmProfile] = useState(null);
13947
+ const runRecommendWithProfile = useCallback(async (p) => {
13796
13948
  const profileHeight = p.height ?? p.heightCm ?? 0;
13797
13949
  const profileWeight = p.weight ?? p.weightKg ?? 0;
13798
- const hasIdentity = profileHeight > 0 && profileWeight > 0;
13799
- if (!hasIdentity) return;
13800
13950
  const hasStored = !!p.measurements && Object.keys(p.measurements).length > 0;
13801
13951
  const storedPhoto = p.photoBase64;
13802
13952
  if (!hasStored && storedPhoto && profileHeight > 0 && snapSubmitRef.current) {
@@ -13820,9 +13970,8 @@ function PrimeStyleTryonInner({
13820
13970
  }
13821
13971
  setSizingResult(null);
13822
13972
  setSizingLoading(true);
13823
- const hasStoredMeasurements = !!p.measurements && Object.keys(p.measurements).length > 0;
13824
- setEstimationDone(hasStoredMeasurements);
13825
- if (hasStoredMeasurements) {
13973
+ setEstimationDone(hasStored);
13974
+ if (hasStored) {
13826
13975
  setPreviewUrl(null);
13827
13976
  setBodyLandmarks(null);
13828
13977
  }
@@ -13855,7 +14004,27 @@ function PrimeStyleTryonInner({
13855
14004
  setEstimationDone(true);
13856
14005
  }).catch(() => {
13857
14006
  }).finally(() => setSizingLoading(false));
13858
- }, [profiles, activeProfileId, effectiveProductId, productTitle, productImage, sizeGuideData, apiUrl]);
14007
+ }, [effectiveProductId, productTitle, productImage, sizeGuideData, apiUrl, previewUrl]);
14008
+ const handleUseActiveProfile = useCallback(async () => {
14009
+ const p = profiles.find((x) => x.id === activeProfileId);
14010
+ if (!p) return;
14011
+ const profileHeight = p.height ?? p.heightCm ?? 0;
14012
+ const profileWeight = p.weight ?? p.weightKg ?? 0;
14013
+ const hasIdentity = profileHeight > 0 && profileWeight > 0;
14014
+ if (!hasIdentity) return;
14015
+ setConfirmProfile(p);
14016
+ }, [profiles, activeProfileId]);
14017
+ const proceedFromConfirmProfile = useCallback(() => {
14018
+ if (!confirmProfile) return;
14019
+ const p = confirmProfile;
14020
+ setConfirmProfile(null);
14021
+ void runRecommendWithProfile(p);
14022
+ }, [confirmProfile, runRecommendWithProfile]);
14023
+ const cancelFromConfirmProfile = useCallback(() => {
14024
+ setConfirmProfile(null);
14025
+ setFormKey((k) => k + 1);
14026
+ setView("body-profile");
14027
+ }, []);
13859
14028
  const applyProfileRef = useRef(() => {
13860
14029
  });
13861
14030
  const handleOpen = useCallback(() => {
@@ -14194,7 +14363,14 @@ function PrimeStyleTryonInner({
14194
14363
  }
14195
14364
  }, [apiUrl, sizingMethod, sizingCountry, heightUnit, weightUnit, sizingUnit, sizeGuide, productTitle, dynamicFields, persistResultToProfile]);
14196
14365
  const handleQuickEstimate = useCallback(async (height, weight, heightUnit2, weightUnit2, gender, age, bodyType, chestProfile, midsectionProfile, hipProfile, bodyImage) => {
14197
- if (!apiRef.current) return;
14366
+ if (!apiRef.current) {
14367
+ const msg = t("SDK not configured. Please refresh and try again.");
14368
+ console.warn("[ps-sdk] handleQuickEstimate BAILED — apiRef is null. API key not loaded.");
14369
+ setErrorMessage(msg);
14370
+ setView("error");
14371
+ onError?.({ message: msg, code: "SDK_NOT_CONFIGURED" });
14372
+ return;
14373
+ }
14198
14374
  getApiUrl(apiUrl);
14199
14375
  getApiKey();
14200
14376
  const SKIP_ESTIMATE_KEYS = /* @__PURE__ */ new Set(["weight", "weightKg", "height", "heightCm"]);
@@ -14251,7 +14427,11 @@ function PrimeStyleTryonInner({
14251
14427
  apiUrl
14252
14428
  });
14253
14429
  if (!apiRef.current || !sseRef.current) {
14430
+ const msg = t("SDK not configured. Please refresh and try again.");
14254
14431
  console.warn("[ps-sdk] handleSnapSubmit BAILED — apiRef or sseRef is null. Check api init.");
14432
+ setErrorMessage(msg);
14433
+ setView("error");
14434
+ onError?.({ message: msg, code: "SDK_NOT_CONFIGURED" });
14255
14435
  return;
14256
14436
  }
14257
14437
  const baseUrl = getApiUrl(apiUrl);
@@ -15230,6 +15410,15 @@ function PrimeStyleTryonInner({
15230
15410
  onCancel: () => setDeleteConfirmId(null),
15231
15411
  t
15232
15412
  }
15413
+ ),
15414
+ confirmProfile && /* @__PURE__ */ jsx(
15415
+ ConfirmMeasurementsModal,
15416
+ {
15417
+ profile: confirmProfile,
15418
+ onProceed: proceedFromConfirmProfile,
15419
+ onEdit: cancelFromConfirmProfile,
15420
+ t
15421
+ }
15233
15422
  )
15234
15423
  ] }) }),
15235
15424
  document.body