@primestyleai/tryon 5.8.57 → 5.9.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.
@@ -9,7 +9,9 @@ interface PhotoStepMobileProps {
9
9
  photoVariant?: "full-body" | "close-up";
10
10
  photoStepHeight?: string;
11
11
  onPhotoStepHeightChange?: (v: string) => void;
12
+ ageConfirmed: boolean | null;
13
+ onAgeConfirmedChange: (v: boolean | null) => void;
12
14
  t: TranslateFn;
13
15
  }
14
- export declare function PhotoStepMobile({ photoPreview, handlePhotoSelect, handleRemovePhoto, onAnalyze, onSwitchToManual, error, photoVariant, photoStepHeight, onPhotoStepHeightChange, t, }: PhotoStepMobileProps): import("react/jsx-runtime").JSX.Element;
16
+ export declare function PhotoStepMobile({ photoPreview, handlePhotoSelect, handleRemovePhoto, onAnalyze, onSwitchToManual, error, photoVariant, photoStepHeight, onPhotoStepHeightChange, ageConfirmed, onAgeConfirmedChange, t, }: PhotoStepMobileProps): import("react/jsx-runtime").JSX.Element;
15
17
  export {};
@@ -1,11 +1,13 @@
1
1
  import type { PrimeStyleClassNames } from "../../types";
2
2
  import type { TranslateFn } from "../../i18n";
3
- export declare function ProcessingView({ previewUrl, progressRef, progressBarRef, progressTextRef, progressStatusRef, cn, t, }: {
3
+ export declare function ProcessingView({ previewUrl, progressRef, progressBarRef, progressTextRef, progressStatusRef, progressEtaRef, progressRingRef, cn, t, }: {
4
4
  previewUrl: string | null;
5
5
  progressRef: React.MutableRefObject<number>;
6
6
  progressBarRef: React.MutableRefObject<HTMLDivElement | null>;
7
7
  progressTextRef: React.MutableRefObject<HTMLSpanElement | null>;
8
8
  progressStatusRef: React.MutableRefObject<HTMLDivElement | null>;
9
+ progressEtaRef: React.MutableRefObject<HTMLSpanElement | null>;
10
+ progressRingRef: React.MutableRefObject<SVGCircleElement | null>;
9
11
  cn: PrimeStyleClassNames;
10
12
  t: TranslateFn;
11
13
  }): import("react/jsx-runtime").JSX.Element;
@@ -9296,9 +9296,10 @@ class ApiClient {
9296
9296
  Authorization: `Bearer ${this.apiKey}`
9297
9297
  };
9298
9298
  }
9299
- async submitTryOn(modelImage, garmentImage, fitInfo) {
9299
+ async submitTryOn(modelImage, garmentImage, fitInfo, category) {
9300
9300
  const body = { modelImage, garmentImage };
9301
9301
  if (fitInfo && fitInfo.length > 0) body.fitInfo = fitInfo;
9302
+ if (category && category !== "apparel") body.category = category;
9302
9303
  const res = await fetch(`${this.baseUrl}/api/v1/tryon`, {
9303
9304
  method: "POST",
9304
9305
  headers: this.headers,
@@ -11508,22 +11509,61 @@ const STYLES$1 = `
11508
11509
  }
11509
11510
 
11510
11511
  .ps-tryon-progress-section {
11511
- display: flex; align-items: center; gap: 0.63vw; width: 100%; max-width: 15.6vw; margin-bottom: 0.83vw;
11512
+ display: flex; align-items: center; gap: 0.63vw; width: 100%; max-width: 18vw; margin-bottom: 0.83vw;
11512
11513
  }
11513
11514
  .ps-tryon-progress-bar-wrap {
11514
11515
  flex: 1; height: 0.31vw; background: var(--ps-border-color); border-radius: 3px; overflow: hidden;
11516
+ position: relative;
11517
+ }
11518
+ .ps-tryon-progress-bar-wrap::after {
11519
+ content: ""; position: absolute; inset: 0;
11520
+ background: linear-gradient(90deg, transparent 0%, rgba(255,255,255,0.45) 50%, transparent 100%);
11521
+ transform: translateX(-100%);
11522
+ animation: ps-progress-shimmer 2s linear infinite;
11523
+ pointer-events: none;
11524
+ }
11525
+ @keyframes ps-progress-shimmer {
11526
+ 0% { transform: translateX(-100%); }
11527
+ 100% { transform: translateX(100%); }
11515
11528
  }
11516
11529
  .ps-tryon-progress-bar-fill {
11517
11530
  height: 100%; background: linear-gradient(90deg, var(--ps-accent), var(--ps-accent-light));
11518
11531
  border-radius: 3px; transition: width 0.3s ease; width: 0%;
11532
+ position: relative; z-index: 1;
11519
11533
  }
11520
11534
  .ps-tryon-progress-pct {
11521
11535
  font-size: 0.68vw; font-weight: 700; color: var(--ps-accent); min-width: 1.88vw; text-align: right;
11522
11536
  font-variant-numeric: tabular-nums;
11523
11537
  }
11524
11538
 
11539
+ /* Circular ETA ring — 48×48 px SVG with a track + progress circle; ETA
11540
+ text centered. strokeDashoffset is driven by the ticker in
11541
+ PrimeStyleTryonInner, so CSS only styles the appearance. */
11542
+ .ps-tryon-progress-ring {
11543
+ position: relative; width: 48px; height: 48px; flex: 0 0 48px;
11544
+ display: flex; align-items: center; justify-content: center;
11545
+ }
11546
+ .ps-tryon-progress-ring svg { transform: rotate(-90deg); }
11547
+ .ps-tryon-progress-ring-track {
11548
+ fill: none; stroke: var(--ps-border-color); stroke-width: 3.5;
11549
+ }
11550
+ .ps-tryon-progress-ring-fill {
11551
+ fill: none; stroke: var(--ps-accent); stroke-width: 3.5;
11552
+ stroke-linecap: round;
11553
+ transition: stroke-dashoffset 0.3s ease;
11554
+ }
11555
+ .ps-tryon-progress-eta {
11556
+ position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
11557
+ font-size: 10px; font-weight: 700; color: var(--ps-accent);
11558
+ font-variant-numeric: tabular-nums; letter-spacing: 0.01em;
11559
+ pointer-events: none;
11560
+ }
11561
+
11525
11562
  @keyframes ps-scale-in { from { opacity: 0; transform: scale(0.8); } to { opacity: 1; transform: scale(1); } }
11526
- .ps-tryon-processing-text { font-size: 0.73vw; color: var(--ps-text-primary); margin: 0 0 0.21vw; }
11563
+ .ps-tryon-processing-text {
11564
+ font-size: 0.73vw; color: var(--ps-text-primary); margin: 0 0 0.21vw;
11565
+ opacity: 1; transition: opacity 0.18s ease;
11566
+ }
11527
11567
  .ps-tryon-processing-sub { font-size: 0.63vw; color: var(--ps-text-secondary); margin: 0; }
11528
11568
 
11529
11569
  /* Result — split layout */
@@ -18374,6 +18414,7 @@ function SizeResultView({
18374
18414
  const allDone = hasPhoto ? sizingDone && tryOnDone : sizingDone;
18375
18415
  const isMobile = useIsMobile();
18376
18416
  const isAccessory = measurementType === "face" || measurementType === "head";
18417
+ const vtoExcluded = measurementType === "foot";
18377
18418
  console.log("[PS-SDK] SizeResultView render:", {
18378
18419
  hasPhoto,
18379
18420
  isSnapProcessing,
@@ -18647,7 +18688,7 @@ function SizeResultView({
18647
18688
  " →"
18648
18689
  ]
18649
18690
  }
18650
- ) : isAccessory ? /* @__PURE__ */ jsxRuntimeExports.jsxs(
18691
+ ) : vtoExcluded ? /* @__PURE__ */ jsxRuntimeExports.jsxs(
18651
18692
  "button",
18652
18693
  {
18653
18694
  className: "ps-tryon-v2-cta",
@@ -18707,7 +18748,7 @@ function SizeResultView({
18707
18748
  onBack: resultImageUrl ? onClose || (() => setView("body-profile")) : () => setView("body-profile"),
18708
18749
  backLabel: t2("Back"),
18709
18750
  internationalSizes: sizingResult?.internationalSizes,
18710
- onTryOn: resultImageUrl || isAccessory ? void 0 : handleSingleTryOn,
18751
+ onTryOn: resultImageUrl || vtoExcluded ? void 0 : handleSingleTryOn,
18711
18752
  continueLabel: resultImageUrl ? t2("Continue Shopping") : void 0,
18712
18753
  tryOnProcessing,
18713
18754
  productImage: resultImageUrl || productImage,
@@ -18754,7 +18795,7 @@ function SizeResultView({
18754
18795
  onBack: resultImageUrl ? onClose || (() => setView("body-profile")) : () => setView("body-profile"),
18755
18796
  backLabel: t2("Back"),
18756
18797
  internationalSizes: sizingResult?.internationalSizes,
18757
- onTryOn: resultImageUrl || isAccessory ? void 0 : handleSingleTryOn,
18798
+ onTryOn: resultImageUrl || vtoExcluded ? void 0 : handleSingleTryOn,
18758
18799
  continueLabel: resultImageUrl ? t2("Continue Shopping") : void 0,
18759
18800
  tryOnProcessing,
18760
18801
  t: t2,
@@ -18764,14 +18805,14 @@ function SizeResultView({
18764
18805
  ] });
18765
18806
  })()
18766
18807
  ),
18767
- showPhotoGuide && !isAccessory && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-tryon-sr-chart-overlay", children: isMobile ? (
18808
+ showPhotoGuide && !vtoExcluded && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-tryon-sr-chart-overlay", children: isMobile ? (
18768
18809
  /* ── Mobile: same layout as the AI-sizing photo step
18769
18810
  (PhotoStepMobile) — title + subtitle, large preview,
18770
18811
  checklist card, primary CTA + RETAKE secondary. ── */
18771
18812
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-bp-wrapper", style: { padding: "16px 16px 0", background: "var(--ps-bg-primary)" }, children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-pm-root", children: [
18772
18813
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-pm-header", children: [
18773
18814
  /* @__PURE__ */ jsxRuntimeExports.jsx("h2", { className: "ps-pm-title", children: t2("Review your photo") }),
18774
- /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "ps-pm-subtitle", children: t2("Ensure your full body is visible for the most accurate virtual try-on.") })
18815
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "ps-pm-subtitle", children: measurementType === "face" ? t2("A clear, front-facing face photo — no glasses on — gives us the most accurate try-on.") : measurementType === "head" ? t2("Face the camera with your head and shoulders in frame, leaving space above your head.") : t2("Ensure your full body is visible for the most accurate virtual try-on.") })
18775
18816
  ] }),
18776
18817
  /* @__PURE__ */ jsxRuntimeExports.jsx(
18777
18818
  "input",
@@ -18822,11 +18863,19 @@ function SizeResultView({
18822
18863
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-pm-checklist-icon", children: /* @__PURE__ */ jsxRuntimeExports.jsx("svg", { viewBox: "0 0 24 24", fill: "currentColor", width: "14", height: "14", children: /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z" }) }) }),
18823
18864
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-pm-checklist-body", children: [
18824
18865
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-pm-checklist-title", children: t2("Checklist for accuracy") }),
18825
- /* @__PURE__ */ jsxRuntimeExports.jsxs("ul", { className: "ps-pm-checklist-items", children: [
18866
+ /* @__PURE__ */ jsxRuntimeExports.jsx("ul", { className: "ps-pm-checklist-items", children: measurementType === "face" ? /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
18867
+ /* @__PURE__ */ jsxRuntimeExports.jsx("li", { children: t2("Face the camera directly at eye level") }),
18868
+ /* @__PURE__ */ jsxRuntimeExports.jsx("li", { children: t2("Remove any glasses you're wearing") }),
18869
+ /* @__PURE__ */ jsxRuntimeExports.jsx("li", { children: t2("Good lighting, plain background") })
18870
+ ] }) : measurementType === "head" ? /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
18871
+ /* @__PURE__ */ jsxRuntimeExports.jsx("li", { children: t2("Head and shoulders in frame") }),
18872
+ /* @__PURE__ */ jsxRuntimeExports.jsx("li", { children: t2("Leave space above your head") }),
18873
+ /* @__PURE__ */ jsxRuntimeExports.jsx("li", { children: t2("Good lighting, plain background") })
18874
+ ] }) : /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
18826
18875
  /* @__PURE__ */ jsxRuntimeExports.jsx("li", { children: t2("Form-fitting clothing is recommended") }),
18827
18876
  /* @__PURE__ */ jsxRuntimeExports.jsx("li", { children: t2("Standing 2-3 meters from camera") }),
18828
18877
  /* @__PURE__ */ jsxRuntimeExports.jsx("li", { children: t2("Neutral background with good lighting") })
18829
- ] })
18878
+ ] }) })
18830
18879
  ] })
18831
18880
  ] }),
18832
18881
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-bpm-spacer" }),
@@ -18906,7 +18955,7 @@ function SizeResultView({
18906
18955
  ] }) : /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
18907
18956
  /* @__PURE__ */ jsxRuntimeExports.jsx(UploadIcon, { size: 32 }),
18908
18957
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { style: { fontSize: "0.85vw", fontWeight: 600, color: "var(--ps-text-primary)", marginTop: "0.5vw" }, children: t2("Upload your photo") }),
18909
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { style: { fontSize: "0.6vw", color: "var(--ps-text-muted)", marginTop: "0.2vw" }, children: t2("Click or drag a full-body photo") })
18958
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { style: { fontSize: "0.6vw", color: "var(--ps-text-muted)", marginTop: "0.2vw" }, children: measurementType === "face" ? t2("Click or drag a close-up face photo") : measurementType === "head" ? t2("Click or drag a head-and-shoulders photo") : t2("Click or drag a full-body photo") })
18910
18959
  ] }),
18911
18960
  /* @__PURE__ */ jsxRuntimeExports.jsx(
18912
18961
  "input",
@@ -18931,7 +18980,23 @@ function SizeResultView({
18931
18980
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { style: { color: "#1c9d4c", fontSize: "0.75vw", fontWeight: 700 }, children: "✓" }),
18932
18981
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { style: { color: "#1c9d4c", fontSize: "0.65vw", fontWeight: 600 }, children: t2("Do") })
18933
18982
  ] }),
18934
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { style: { fontSize: "0.58vw", color: "var(--ps-text-primary)", lineHeight: 1.8 }, children: [
18983
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { style: { fontSize: "0.58vw", color: "var(--ps-text-primary)", lineHeight: 1.8 }, children: measurementType === "face" ? /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
18984
+ t2("Face the camera directly, centered in frame"),
18985
+ /* @__PURE__ */ jsxRuntimeExports.jsx("br", {}),
18986
+ t2("Use natural, even lighting (e.g. near a window)"),
18987
+ /* @__PURE__ */ jsxRuntimeExports.jsx("br", {}),
18988
+ t2("Keep hair away from your face and ears"),
18989
+ /* @__PURE__ */ jsxRuntimeExports.jsx("br", {}),
18990
+ t2("Choose a plain, light background")
18991
+ ] }) : measurementType === "head" ? /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
18992
+ t2("Face the camera with head and shoulders in frame"),
18993
+ /* @__PURE__ */ jsxRuntimeExports.jsx("br", {}),
18994
+ t2("Leave some space above your head"),
18995
+ /* @__PURE__ */ jsxRuntimeExports.jsx("br", {}),
18996
+ t2("Use natural, even lighting"),
18997
+ /* @__PURE__ */ jsxRuntimeExports.jsx("br", {}),
18998
+ t2("Choose a plain, light background")
18999
+ ] }) : /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
18935
19000
  t2("Stand facing the camera with your full body in frame"),
18936
19001
  /* @__PURE__ */ jsxRuntimeExports.jsx("br", {}),
18937
19002
  t2("Use natural or even lighting"),
@@ -18939,14 +19004,30 @@ function SizeResultView({
18939
19004
  t2("Wear fitted, simple clothing"),
18940
19005
  /* @__PURE__ */ jsxRuntimeExports.jsx("br", {}),
18941
19006
  t2("Stand straight and still, arms relaxed")
18942
- ] })
19007
+ ] }) })
18943
19008
  ] }),
18944
19009
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { style: { background: "#ffe2e2", borderRadius: "0.5vw", padding: "0.6vw 0.8vw" }, children: [
18945
19010
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { style: { display: "flex", alignItems: "center", gap: "0.3vw", marginBottom: "0.3vw" }, children: [
18946
19011
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { style: { color: "#e7000b", fontSize: "0.75vw", fontWeight: 700 }, children: "✗" }),
18947
19012
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { style: { color: "#e7000b", fontSize: "0.65vw", fontWeight: 600 }, children: t2("Don't") })
18948
19013
  ] }),
18949
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { style: { fontSize: "0.58vw", color: "var(--ps-text-primary)", lineHeight: 1.8 }, children: [
19014
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { style: { fontSize: "0.58vw", color: "var(--ps-text-primary)", lineHeight: 1.8 }, children: measurementType === "face" ? /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
19015
+ t2("Don't wear sunglasses or a hat in the photo"),
19016
+ /* @__PURE__ */ jsxRuntimeExports.jsx("br", {}),
19017
+ t2("Don't tilt or turn your head"),
19018
+ /* @__PURE__ */ jsxRuntimeExports.jsx("br", {}),
19019
+ t2("Don't use strong backlighting or flash"),
19020
+ /* @__PURE__ */ jsxRuntimeExports.jsx("br", {}),
19021
+ t2("Don't apply filters or edits")
19022
+ ] }) : measurementType === "head" ? /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
19023
+ t2("Don't wear a hat in the photo"),
19024
+ /* @__PURE__ */ jsxRuntimeExports.jsx("br", {}),
19025
+ t2("Don't crop out the top of your head"),
19026
+ /* @__PURE__ */ jsxRuntimeExports.jsx("br", {}),
19027
+ t2("Don't use strong backlighting or flash"),
19028
+ /* @__PURE__ */ jsxRuntimeExports.jsx("br", {}),
19029
+ t2("Don't apply filters or edits")
19030
+ ] }) : /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
18950
19031
  t2("Don't wear loose or baggy clothing"),
18951
19032
  /* @__PURE__ */ jsxRuntimeExports.jsx("br", {}),
18952
19033
  t2("Don't sit, pose, or bend"),
@@ -18954,14 +19035,14 @@ function SizeResultView({
18954
19035
  t2("Don't take mirror photos or selfies"),
18955
19036
  /* @__PURE__ */ jsxRuntimeExports.jsx("br", {}),
18956
19037
  t2("Don't apply filters or edits")
18957
- ] })
19038
+ ] }) })
18958
19039
  ] }),
18959
19040
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { style: { background: "rgba(59,130,246,0.08)", border: "1px solid rgba(59,130,246,0.2)", borderRadius: "0.5vw", padding: "0.5vw 0.8vw" }, children: [
18960
19041
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { style: { display: "flex", alignItems: "center", gap: "0.3vw", marginBottom: "0.2vw" }, children: [
18961
19042
  /* @__PURE__ */ jsxRuntimeExports.jsx(SparkleIcon, { size: 12 }),
18962
19043
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { style: { color: "var(--ps-accent)", fontSize: "0.65vw", fontWeight: 700 }, children: t2("Pro Tip") })
18963
19044
  ] }),
18964
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { style: { fontSize: "0.55vw", color: "var(--ps-text-secondary)", lineHeight: 1.7 }, children: t2("Our AI works best with front-facing, full-body photos in fitted clothing. Better photos = more accurate virtual try-on!") })
19045
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { style: { fontSize: "0.55vw", color: "var(--ps-text-secondary)", lineHeight: 1.7 }, children: measurementType === "face" ? t2("A clear, well-lit face photo gives the most accurate eyewear try-on.") : measurementType === "head" ? t2("A clear head-and-shoulders photo with space above your head gives the most accurate headwear try-on.") : t2("Our AI works best with front-facing, full-body photos in fitted clothing. Better photos = more accurate virtual try-on!") })
18965
19046
  ] })
18966
19047
  ] })
18967
19048
  ] }),
@@ -19090,12 +19171,16 @@ function UploadView({
19090
19171
  }
19091
19172
  ) });
19092
19173
  }
19174
+ const RING_RADIUS = 20;
19175
+ const RING_CIRCUMFERENCE = 2 * Math.PI * RING_RADIUS;
19093
19176
  function ProcessingView({
19094
19177
  previewUrl,
19095
19178
  progressRef,
19096
19179
  progressBarRef,
19097
19180
  progressTextRef,
19098
19181
  progressStatusRef,
19182
+ progressEtaRef,
19183
+ progressRingRef,
19099
19184
  cn,
19100
19185
  t: t2
19101
19186
  }) {
@@ -19110,6 +19195,16 @@ function ProcessingView({
19110
19195
  const statusCb = reactExports.useCallback((el2) => {
19111
19196
  progressStatusRef.current = el2;
19112
19197
  }, []);
19198
+ const etaCb = reactExports.useCallback((el2) => {
19199
+ progressEtaRef.current = el2;
19200
+ }, []);
19201
+ const ringCb = reactExports.useCallback((el2) => {
19202
+ progressRingRef.current = el2;
19203
+ if (el2) {
19204
+ const offset = RING_CIRCUMFERENCE * (1 - Math.round(progressRef.current) / 100);
19205
+ el2.style.strokeDashoffset = String(offset);
19206
+ }
19207
+ }, []);
19113
19208
  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-tryon-processing", children: [
19114
19209
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-tryon-processing-image-wrap", children: [
19115
19210
  previewUrl && /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
@@ -19120,6 +19215,32 @@ function ProcessingView({
19120
19215
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-tryon-scan-overlay" })
19121
19216
  ] }),
19122
19217
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-tryon-progress-section", children: [
19218
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-tryon-progress-ring", children: [
19219
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("svg", { viewBox: "0 0 48 48", width: "48", height: "48", "aria-hidden": "true", children: [
19220
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
19221
+ "circle",
19222
+ {
19223
+ cx: "24",
19224
+ cy: "24",
19225
+ r: RING_RADIUS,
19226
+ className: "ps-tryon-progress-ring-track"
19227
+ }
19228
+ ),
19229
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
19230
+ "circle",
19231
+ {
19232
+ ref: ringCb,
19233
+ cx: "24",
19234
+ cy: "24",
19235
+ r: RING_RADIUS,
19236
+ className: "ps-tryon-progress-ring-fill",
19237
+ strokeDasharray: RING_CIRCUMFERENCE,
19238
+ strokeDashoffset: RING_CIRCUMFERENCE
19239
+ }
19240
+ )
19241
+ ] }),
19242
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { ref: etaCb, className: "ps-tryon-progress-eta", children: `~22s` })
19243
+ ] }),
19123
19244
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-tryon-progress-bar-wrap", children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { ref: barCb, className: "ps-tryon-progress-bar-fill" }) }),
19124
19245
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { ref: pctCb, className: "ps-tryon-progress-pct", children: "0%" })
19125
19246
  ] }),
@@ -20785,12 +20906,13 @@ function PhotoStepMobile({
20785
20906
  photoVariant = "full-body",
20786
20907
  photoStepHeight,
20787
20908
  onPhotoStepHeightChange,
20909
+ ageConfirmed,
20910
+ onAgeConfirmedChange,
20788
20911
  t: t2
20789
20912
  }) {
20790
20913
  const isCloseUp = photoVariant === "close-up";
20791
20914
  const fileRef = reactExports.useRef(null);
20792
20915
  const hasPhoto = !!photoPreview;
20793
- const [ageConfirmed, setAgeConfirmed] = reactExports.useState(null);
20794
20916
  const gated = !hasPhoto && ageConfirmed !== true;
20795
20917
  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-pm-root", children: [
20796
20918
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-pm-header", children: [
@@ -20842,14 +20964,14 @@ function PhotoStepMobile({
20842
20964
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-pm-age-gate-eyebrow", children: t2("AGE VERIFICATION") }),
20843
20965
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-pm-age-gate-question", children: t2("Is the person in this photo 18 years or older?") }),
20844
20966
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-pm-age-gate-actions", children: [
20845
- /* @__PURE__ */ jsxRuntimeExports.jsx("button", { type: "button", className: "ps-pm-age-gate-btn ps-pm-age-gate-btn-primary", onClick: () => setAgeConfirmed(true), children: t2("Yes") }),
20846
- /* @__PURE__ */ jsxRuntimeExports.jsx("button", { type: "button", className: "ps-pm-age-gate-btn ps-pm-age-gate-btn-secondary", onClick: () => setAgeConfirmed(false), children: t2("No") })
20967
+ /* @__PURE__ */ jsxRuntimeExports.jsx("button", { type: "button", className: "ps-pm-age-gate-btn ps-pm-age-gate-btn-primary", onClick: () => onAgeConfirmedChange(true), children: t2("Yes") }),
20968
+ /* @__PURE__ */ jsxRuntimeExports.jsx("button", { type: "button", className: "ps-pm-age-gate-btn ps-pm-age-gate-btn-secondary", onClick: () => onAgeConfirmedChange(false), children: t2("No") })
20847
20969
  ] })
20848
20970
  ] }) }),
20849
20971
  ageConfirmed === false && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-pm-age-gate", role: "alert", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-pm-age-gate-card", children: [
20850
20972
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-pm-age-gate-eyebrow ps-pm-age-gate-eyebrow-blocked", children: t2("UPLOAD NOT ALLOWED") }),
20851
20973
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-pm-age-gate-question", children: t2("For your safety, we cannot process photos of people under 18.") }),
20852
- /* @__PURE__ */ jsxRuntimeExports.jsx("button", { type: "button", className: "ps-pm-age-gate-btn ps-pm-age-gate-btn-secondary", onClick: () => setAgeConfirmed(null), children: t2("Go back") })
20974
+ /* @__PURE__ */ jsxRuntimeExports.jsx("button", { type: "button", className: "ps-pm-age-gate-btn ps-pm-age-gate-btn-secondary", onClick: () => onAgeConfirmedChange(null), children: t2("Go back") })
20853
20975
  ] }) })
20854
20976
  ] }) }),
20855
20977
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-pm-legal-notice", children: [
@@ -21317,6 +21439,11 @@ function BodyProfileView({
21317
21439
  error,
21318
21440
  photoStepHeight,
21319
21441
  onPhotoStepHeightChange: setPhotoStepHeight,
21442
+ ageConfirmed,
21443
+ onAgeConfirmedChange: (v2) => {
21444
+ setAgeConfirmed(v2);
21445
+ if (v2 === true) setError("");
21446
+ },
21320
21447
  t: t2
21321
21448
  }
21322
21449
  ) });
@@ -22063,6 +22190,11 @@ function AccessorySizeView({
22063
22190
  onSwitchToManual: () => setStep("manual"),
22064
22191
  error,
22065
22192
  photoVariant,
22193
+ ageConfirmed,
22194
+ onAgeConfirmedChange: (v2) => {
22195
+ setAgeConfirmed(v2);
22196
+ if (v2 === true) setError("");
22197
+ },
22066
22198
  t: t2
22067
22199
  }
22068
22200
  ) });
@@ -22473,13 +22605,26 @@ function buildFieldsFromSizeGuide$2(sizeGuide) {
22473
22605
  const req = sizeGuide?.requiredFields;
22474
22606
  if (!req || req.length === 0) return FALLBACK_FOOT_FIELDS;
22475
22607
  const SIZE_CODE_KEYS = /* @__PURE__ */ new Set(["size", "country", "eu", "__skip__", "shoeEU", "shoeUS", "shoeUK", "shoeJP"]);
22476
- const out = req.filter((f2) => !SIZE_CODE_KEYS.has(f2.key) && f2.unit !== "size").map((f2) => ({
22477
- key: f2.key,
22478
- label: f2.label || f2.key,
22479
- placeholder: { cm: f2.placeholder || "", in: f2.placeholder || "" },
22480
- min: 0,
22481
- step: 0.5
22482
- }));
22608
+ const defaultPlaceholderFor = (key, label) => {
22609
+ const exact = FALLBACK_FOOT_FIELDS.find((f2) => f2.key === key);
22610
+ if (exact) return exact.placeholder;
22611
+ const looksLikeFoot = /foot|length/i.test(key) || /foot|length/i.test(label);
22612
+ if (looksLikeFoot) return FALLBACK_FOOT_FIELDS[0].placeholder;
22613
+ return { cm: "", in: "" };
22614
+ };
22615
+ const out = req.filter((f2) => !SIZE_CODE_KEYS.has(f2.key) && f2.unit !== "size").map((f2) => {
22616
+ const fallback = defaultPlaceholderFor(f2.key, f2.label || "");
22617
+ return {
22618
+ key: f2.key,
22619
+ label: f2.label || f2.key,
22620
+ placeholder: {
22621
+ cm: f2.placeholder || fallback.cm,
22622
+ in: f2.placeholder || fallback.in
22623
+ },
22624
+ min: 0,
22625
+ step: 0.5
22626
+ };
22627
+ });
22483
22628
  return out.length > 0 ? out : FALLBACK_FOOT_FIELDS;
22484
22629
  }
22485
22630
  function FootSizeView(props) {
@@ -22607,6 +22752,12 @@ function detectMeasurementType(title) {
22607
22752
  if (/\b(sunglass|sunglasses|eyewear|eyeglasses|glasses|spectacles|optical|goggles|frames|aviator|wayfarer|lens)\b/.test(t2)) return "face";
22608
22753
  return "body";
22609
22754
  }
22755
+ function measurementTypeToVtoCategory(type) {
22756
+ if (type === "face") return "sunglasses";
22757
+ if (type === "head") return "hat";
22758
+ if (type === "body") return "apparel";
22759
+ return null;
22760
+ }
22610
22761
  function PrimeStyleTryonInner({
22611
22762
  productImage,
22612
22763
  productTitle = "Product",
@@ -22712,15 +22863,22 @@ function PrimeStyleTryonInner({
22712
22863
  if (pollingRef.current) clearInterval(pollingRef.current);
22713
22864
  };
22714
22865
  }, [apiUrl]);
22866
+ const TARGET_SECONDS = 22;
22715
22867
  const progressRef = reactExports.useRef(0);
22716
22868
  const progressBarRef = reactExports.useRef(null);
22717
22869
  const progressTextRef = reactExports.useRef(null);
22718
22870
  const progressStatusRef = reactExports.useRef(null);
22871
+ const progressEtaRef = reactExports.useRef(null);
22872
+ const progressRingRef = reactExports.useRef(null);
22873
+ const progressStartTsRef = reactExports.useRef(null);
22874
+ const progressLastStageRef = reactExports.useRef("");
22719
22875
  const progressIntervalRef = reactExports.useRef(null);
22720
22876
  reactExports.useEffect(() => {
22721
22877
  if (view === "processing") {
22722
22878
  if (progressIntervalRef.current) return;
22723
22879
  progressRef.current = 0;
22880
+ progressStartTsRef.current = Date.now();
22881
+ progressLastStageRef.current = "";
22724
22882
  const statuses = [
22725
22883
  { at: 0, text: t2("Preparing your image...") },
22726
22884
  { at: 15, text: t2("Analyzing body proportions...") },
@@ -22729,17 +22887,35 @@ function PrimeStyleTryonInner({
22729
22887
  { at: 75, text: t2("Refining details...") },
22730
22888
  { at: 90, text: t2("Almost there...") }
22731
22889
  ];
22890
+ const RING_CIRCUMFERENCE2 = 2 * Math.PI * 20;
22732
22891
  progressIntervalRef.current = setInterval(() => {
22733
- const p2 = progressRef.current;
22734
- if (p2 >= 100) return;
22735
- const increment = p2 < 30 ? 1.2 : p2 < 60 ? 0.8 : p2 < 80 ? 0.4 : p2 < 95 ? 0.15 : 0;
22736
- progressRef.current = Math.min(p2 + increment, 95);
22737
- const val = Math.round(progressRef.current);
22892
+ if (completedRef.current) return;
22893
+ const startTs = progressStartTsRef.current || Date.now();
22894
+ const elapsed = (Date.now() - startTs) / 1e3;
22895
+ const target = Math.min(95, elapsed / TARGET_SECONDS * 100);
22896
+ progressRef.current = target;
22897
+ const val = Math.round(target);
22738
22898
  if (progressBarRef.current) progressBarRef.current.style.width = `${val}%`;
22739
22899
  if (progressTextRef.current) progressTextRef.current.textContent = `${val}%`;
22900
+ if (progressRingRef.current) {
22901
+ const offset = RING_CIRCUMFERENCE2 * (1 - target / 100);
22902
+ progressRingRef.current.style.strokeDashoffset = String(offset);
22903
+ }
22904
+ if (progressEtaRef.current) {
22905
+ const remaining = Math.max(0, TARGET_SECONDS - Math.floor(elapsed));
22906
+ progressEtaRef.current.textContent = elapsed >= TARGET_SECONDS ? t2("Finalizing...") : `~${remaining}s`;
22907
+ }
22740
22908
  if (progressStatusRef.current) {
22741
22909
  const status = [...statuses].reverse().find((s) => val >= s.at);
22742
- if (status) progressStatusRef.current.textContent = status.text;
22910
+ if (status && status.text !== progressLastStageRef.current) {
22911
+ const el2 = progressStatusRef.current;
22912
+ el2.style.opacity = "0";
22913
+ setTimeout(() => {
22914
+ el2.textContent = status.text;
22915
+ el2.style.opacity = "1";
22916
+ }, 180);
22917
+ progressLastStageRef.current = status.text;
22918
+ }
22743
22919
  }
22744
22920
  }, 200);
22745
22921
  return () => {
@@ -22751,8 +22927,9 @@ function PrimeStyleTryonInner({
22751
22927
  clearInterval(progressIntervalRef.current);
22752
22928
  progressIntervalRef.current = null;
22753
22929
  }
22930
+ progressStartTsRef.current = null;
22754
22931
  }
22755
- }, [view]);
22932
+ }, [view, t2]);
22756
22933
  reactExports.useEffect(() => {
22757
22934
  return () => {
22758
22935
  if (previewUrl) URL.revokeObjectURL(previewUrl);
@@ -23079,7 +23256,16 @@ function PrimeStyleTryonInner({
23079
23256
  progressRef.current = 100;
23080
23257
  if (progressBarRef.current) progressBarRef.current.style.width = "100%";
23081
23258
  if (progressTextRef.current) progressTextRef.current.textContent = "100%";
23082
- if (progressStatusRef.current) progressStatusRef.current.textContent = t2("Complete!");
23259
+ if (progressRingRef.current) progressRingRef.current.style.strokeDashoffset = "0";
23260
+ if (progressEtaRef.current) progressEtaRef.current.textContent = t2("Done");
23261
+ if (progressStatusRef.current) {
23262
+ const el2 = progressStatusRef.current;
23263
+ el2.style.opacity = "0";
23264
+ setTimeout(() => {
23265
+ el2.textContent = t2("Complete!");
23266
+ el2.style.opacity = "1";
23267
+ }, 180);
23268
+ }
23083
23269
  cleanupJob();
23084
23270
  setTryOnProcessing(false);
23085
23271
  onComplete?.({ jobId: update.galleryId, imageUrl: update.imageUrl });
@@ -23488,25 +23674,29 @@ function PrimeStyleTryonInner({
23488
23674
  }
23489
23675
  completedRef.current = false;
23490
23676
  setTryOnProcessing(true);
23677
+ const vtoCategory = measurementTypeToVtoCategory(detectMeasurementType(productTitle));
23678
+ const isApparel = vtoCategory === "apparel";
23491
23679
  const previewObjUrl = (overrideFile ? null : previewUrl) || URL.createObjectURL(file);
23492
23680
  if (overrideFile || !previewUrl) setPreviewUrl(previewObjUrl);
23493
23681
  modelPoseRef.current = null;
23494
23682
  setBodyLandmarks(null);
23495
- detectMeasurementLines(previewObjUrl).then((lines) => {
23496
- modelPoseRef.current = lines;
23497
- }).catch(() => {
23498
- });
23499
- detectBodyLandmarks(previewObjUrl).then((lm) => {
23500
- setBodyLandmarks(lm);
23501
- }).catch(() => {
23502
- });
23683
+ if (isApparel) {
23684
+ detectMeasurementLines(previewObjUrl).then((lines) => {
23685
+ modelPoseRef.current = lines;
23686
+ }).catch(() => {
23687
+ });
23688
+ detectBodyLandmarks(previewObjUrl).then((lm) => {
23689
+ setBodyLandmarks(lm);
23690
+ }).catch(() => {
23691
+ });
23692
+ }
23503
23693
  try {
23504
23694
  const modelImage = await compressImage(file);
23505
23695
  let fitInfo;
23506
- if (sizingResult?.matchDetails?.length) {
23696
+ if (isApparel && sizingResult?.matchDetails?.length) {
23507
23697
  fitInfo = buildFitInfo(sizingResult.matchDetails, modelPoseRef.current);
23508
23698
  }
23509
- const response = await apiRef.current.submitTryOn(modelImage, productImage, fitInfo);
23699
+ const response = await apiRef.current.submitTryOn(modelImage, productImage, fitInfo, vtoCategory ?? "apparel");
23510
23700
  onProcessing?.(response.jobId);
23511
23701
  unsubRef.current = sseRef.current.onJob(response.jobId, handleVtoUpdate);
23512
23702
  let attempts = 0;
@@ -23537,11 +23727,13 @@ function PrimeStyleTryonInner({
23537
23727
  setView("error");
23538
23728
  onError?.({ message, code });
23539
23729
  }
23540
- }, [selectedFile, productImage, sizingResult, onProcessing, onError, handleVtoUpdate]);
23730
+ }, [selectedFile, productImage, productTitle, sizingResult, onProcessing, onError, handleVtoUpdate]);
23541
23731
  const handleRetryWithFit = reactExports.useCallback(async (fitInfo) => {
23542
23732
  if (!selectedFile || !apiRef.current || !sseRef.current) return;
23543
23733
  setRetryLoading(true);
23544
- if (modelPoseRef.current) {
23734
+ const vtoCategory = measurementTypeToVtoCategory(detectMeasurementType(productTitle));
23735
+ const isApparel = vtoCategory === "apparel";
23736
+ if (isApparel && modelPoseRef.current) {
23545
23737
  const AREA_MAP = {
23546
23738
  chest: "chest",
23547
23739
  bust: "chest",
@@ -23567,7 +23759,8 @@ function PrimeStyleTryonInner({
23567
23759
  pollingRef.current = null;
23568
23760
  }
23569
23761
  const modelImage = await compressImage(selectedFile);
23570
- const response = await apiRef.current.submitTryOn(modelImage, productImage, fitInfo);
23762
+ const outboundFitInfo = isApparel ? fitInfo : void 0;
23763
+ const response = await apiRef.current.submitTryOn(modelImage, productImage, outboundFitInfo, vtoCategory ?? "apparel");
23571
23764
  unsubRef.current = sseRef.current.onJob(response.jobId, (update) => {
23572
23765
  if (update.status === "completed" && update.imageUrl) {
23573
23766
  setResultImageUrl(update.imageUrl);
@@ -23632,7 +23825,7 @@ function PrimeStyleTryonInner({
23632
23825
  } catch {
23633
23826
  setRetryLoading(false);
23634
23827
  }
23635
- }, [selectedFile, productImage]);
23828
+ }, [selectedFile, productImage, productTitle]);
23636
23829
  const handleDownload = reactExports.useCallback(() => {
23637
23830
  if (!resultImageUrl) return;
23638
23831
  if (resultImageUrl.startsWith("data:")) {
@@ -24074,6 +24267,8 @@ function PrimeStyleTryonInner({
24074
24267
  progressBarRef,
24075
24268
  progressTextRef,
24076
24269
  progressStatusRef,
24270
+ progressEtaRef,
24271
+ progressRingRef,
24077
24272
  cn,
24078
24273
  t: t2
24079
24274
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@primestyleai/tryon",
3
- "version": "5.8.57",
3
+ "version": "5.9.0",
4
4
  "description": "PrimeStyle Virtual Try-On SDK — React component & Web Component",
5
5
  "type": "module",
6
6
  "main": "dist/primestyle-tryon.js",
@@ -60,8 +60,5 @@
60
60
  "terser": "^5.31.0",
61
61
  "typescript": "^5.5.0",
62
62
  "vite": "^5.4.0"
63
- },
64
- "dependencies": {
65
- "@primestyleai/tryon": "^5.8.46"
66
63
  }
67
64
  }