@primestyleai/tryon 5.10.171 → 5.10.172

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,14 @@
1
+ import type { BodyLandmarks } from "../../pose-detect";
2
+ export interface PoseOverlayProps {
3
+ landmarks: BodyLandmarks;
4
+ /** Image width in pixels — used for the SVG viewBox so the overlay
5
+ * scales with the underlying photo via `preserveAspectRatio="xMidYMid meet"`. */
6
+ width: number;
7
+ height: number;
8
+ /** Optional `className`/`style` to position the SVG. Defaults to
9
+ * absolutely-filling the parent container, which is what both
10
+ * consumers want. */
11
+ className?: string;
12
+ style?: React.CSSProperties;
13
+ }
14
+ export declare function PoseOverlay({ landmarks, width, height, className, style }: PoseOverlayProps): import("react/jsx-runtime").JSX.Element;
@@ -10361,20 +10361,28 @@ function getCachedMediaPipe() {
10361
10361
  return cachedMP;
10362
10362
  }
10363
10363
  async function recommendForProduct(input) {
10364
- const log = (...args) => console.log("[ps-sdk:recommend]", ...args);
10364
+ const callId = (() => {
10365
+ if (typeof window === "undefined") return 0;
10366
+ const w2 = window;
10367
+ w2.__psSdkDiag = w2.__psSdkDiag || {};
10368
+ w2.__psSdkDiag.recommendCalls = (w2.__psSdkDiag.recommendCalls || 0) + 1;
10369
+ return w2.__psSdkDiag.recommendCalls;
10370
+ })();
10371
+ const log = (...args) => console.log(`[ps-sdk:recommend#${callId}]`, ...args);
10372
+ const t0 = Date.now();
10373
+ log("ENTER", { productId: input.productId, apiUrl: input.apiUrl, skipCache: true });
10365
10374
  const profile = input.profile ?? getActiveProfile();
10366
10375
  if (!profile) {
10367
10376
  log("no active profile — returning null");
10368
10377
  return null;
10369
10378
  }
10370
- log("start", {
10371
- productId: input.productId,
10379
+ log("profile resolved", {
10372
10380
  profileId: profile.id,
10373
10381
  profileName: profile.name,
10374
10382
  hasMeasurements: !!profile.measurements && Object.keys(profile.measurements || {}).length,
10375
10383
  measurementsCount: Object.keys(profile.measurements || {}).length
10376
10384
  });
10377
- log("cache MISS — calling backend");
10385
+ log(`cache MISS — calling backend (elapsed in pre-flight: ${Date.now() - t0}ms)`);
10378
10386
  let apiKey;
10379
10387
  try {
10380
10388
  apiKey = input.apiKey ?? getApiKey();
@@ -10389,6 +10397,8 @@ async function recommendForProduct(input) {
10389
10397
  }
10390
10398
  let sizeGuide = null;
10391
10399
  if (input.sizeGuideData != null) {
10400
+ const tSg = Date.now();
10401
+ log("→ POST /api/v1/sizing/sizeguide");
10392
10402
  try {
10393
10403
  const sgRes = await fetch(`${apiUrl}/api/v1/sizing/sizeguide`, {
10394
10404
  method: "POST",
@@ -10400,15 +10410,15 @@ async function recommendForProduct(input) {
10400
10410
  });
10401
10411
  if (sgRes.ok) {
10402
10412
  sizeGuide = await sgRes.json();
10403
- log("sizeguide OK", { found: sizeGuide?.found, sectionCount: Object.keys(sizeGuide?.sections || {}).length });
10413
+ log(`← sizeguide OK in ${Date.now() - tSg}ms`, { found: sizeGuide?.found, sectionCount: Object.keys(sizeGuide?.sections || {}).length });
10404
10414
  } else {
10405
- log("sizeguide FAILED", sgRes.status, sgRes.statusText);
10415
+ log(`← sizeguide FAILED in ${Date.now() - tSg}ms`, sgRes.status, sgRes.statusText);
10406
10416
  }
10407
10417
  } catch (e) {
10408
- log("sizeguide threw", e);
10418
+ log(`← sizeguide threw in ${Date.now() - tSg}ms`, e);
10409
10419
  }
10410
10420
  } else {
10411
- log("no sizeGuideData provided");
10421
+ log("no sizeGuideData provided — skipping /sizing/sizeguide");
10412
10422
  }
10413
10423
  const measurements = {
10414
10424
  gender: profile.gender,
@@ -10448,7 +10458,8 @@ async function recommendForProduct(input) {
10448
10458
  if (sizeGuide && sizeGuide.found) {
10449
10459
  payload.sizeGuide = sizeGuide;
10450
10460
  }
10451
- log("calling /sizing/recommend", { measurements: Object.keys(measurements), hasSizeGuide: !!payload.sizeGuide });
10461
+ log(" POST /api/v1/sizing/recommend", { measurements: Object.keys(measurements), hasSizeGuide: !!payload.sizeGuide });
10462
+ const tRec = Date.now();
10452
10463
  let result = null;
10453
10464
  try {
10454
10465
  const res = await fetch(`${apiUrl}/api/v1/sizing/recommend`, {
@@ -10457,17 +10468,17 @@ async function recommendForProduct(input) {
10457
10468
  body: JSON.stringify(payload)
10458
10469
  });
10459
10470
  if (!res.ok) {
10460
- log("recommend FAILED", res.status, res.statusText);
10471
+ log(`← recommend FAILED in ${Date.now() - tRec}ms`, res.status, res.statusText);
10461
10472
  return null;
10462
10473
  }
10463
10474
  result = await res.json();
10464
- log("recommend OK", {
10475
+ log(`← recommend OK in ${Date.now() - tRec}ms (total fn ${Date.now() - t0}ms)`, {
10465
10476
  recommendedSize: result?.recommendedSize,
10466
10477
  sectionKeys: result?.sections ? Object.keys(result.sections) : null,
10467
10478
  sections: result?.sections
10468
10479
  });
10469
10480
  } catch (e) {
10470
- log("recommend threw", e);
10481
+ log(`← recommend threw in ${Date.now() - tRec}ms`, e);
10471
10482
  return null;
10472
10483
  }
10473
10484
  if (!result || !result.recommendedSize) {
@@ -19314,8 +19325,6 @@ function useIsMobile() {
19314
19325
  return isMobile;
19315
19326
  }
19316
19327
  const SKELETON_CONNECTIONS$1 = [
19317
- // Head → torso. Bridges the gap between the nose dot and the shoulder
19318
- // line — without these the head reads as floating above the body.
19319
19328
  ["nose", "leftShoulder"],
19320
19329
  ["nose", "rightShoulder"],
19321
19330
  ["leftShoulder", "rightShoulder"],
@@ -19339,13 +19348,18 @@ function isVisible(p2) {
19339
19348
  if (pt2.x < 1e-3 && pt2.y < 1e-3) return false;
19340
19349
  return true;
19341
19350
  }
19342
- function MobileSkeleton({ landmarks, w: w2, h }) {
19351
+ function PoseOverlay({ landmarks, width, height, className, style }) {
19352
+ const longEdge = Math.max(width, height);
19353
+ const stroke = Math.max(2, longEdge / 260);
19354
+ const dotR = Math.max(3, longEdge / 200);
19343
19355
  return /* @__PURE__ */ jsxRuntimeExports.jsxs(
19344
19356
  "svg",
19345
19357
  {
19346
- className: "ps-msc-pose-overlay",
19347
- viewBox: `0 0 ${w2} ${h}`,
19358
+ className,
19359
+ viewBox: `0 0 ${width} ${height}`,
19348
19360
  preserveAspectRatio: "xMidYMid meet",
19361
+ style: style ?? { position: "absolute", inset: 0, width: "100%", height: "100%", pointerEvents: "none" },
19362
+ "aria-hidden": "true",
19349
19363
  children: [
19350
19364
  SKELETON_CONNECTIONS$1.map(([a, b], i) => {
19351
19365
  const pa2 = landmarks[a];
@@ -19354,12 +19368,12 @@ function MobileSkeleton({ landmarks, w: w2, h }) {
19354
19368
  return /* @__PURE__ */ jsxRuntimeExports.jsx(
19355
19369
  "line",
19356
19370
  {
19357
- x1: pa2.x * w2,
19358
- y1: pa2.y * h,
19359
- x2: pb2.x * w2,
19360
- y2: pb2.y * h,
19371
+ x1: pa2.x * width,
19372
+ y1: pa2.y * height,
19373
+ x2: pb2.x * width,
19374
+ y2: pb2.y * height,
19361
19375
  stroke: "rgba(100,210,255,0.9)",
19362
- strokeWidth: "4",
19376
+ strokeWidth: stroke,
19363
19377
  strokeLinecap: "round"
19364
19378
  },
19365
19379
  `l-${i}`
@@ -19367,31 +19381,24 @@ function MobileSkeleton({ landmarks, w: w2, h }) {
19367
19381
  }),
19368
19382
  Object.entries(landmarks).filter(([, v2]) => isVisible(v2)).map(([key, v2]) => {
19369
19383
  const p2 = v2;
19370
- return /* @__PURE__ */ jsxRuntimeExports.jsxs("g", { children: [
19371
- /* @__PURE__ */ jsxRuntimeExports.jsx(
19372
- "circle",
19373
- {
19374
- cx: p2.x * w2,
19375
- cy: p2.y * h,
19376
- r: 14,
19377
- fill: "rgba(100,210,255,0.30)"
19378
- }
19379
- ),
19380
- /* @__PURE__ */ jsxRuntimeExports.jsx(
19381
- "circle",
19382
- {
19383
- cx: p2.x * w2,
19384
- cy: p2.y * h,
19385
- r: 9,
19386
- fill: "rgba(100,210,255,0.95)"
19387
- }
19388
- )
19389
- ] }, key);
19384
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(
19385
+ "circle",
19386
+ {
19387
+ cx: p2.x * width,
19388
+ cy: p2.y * height,
19389
+ r: dotR,
19390
+ fill: "rgba(100,210,255,0.95)"
19391
+ },
19392
+ key
19393
+ );
19390
19394
  })
19391
19395
  ]
19392
19396
  }
19393
19397
  );
19394
19398
  }
19399
+ function MobileSkeleton({ landmarks, w: w2, h }) {
19400
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(PoseOverlay, { landmarks, width: w2, height: h, className: "ps-msc-pose-overlay", style: { position: "absolute", inset: 0, width: "100%", height: "100%", pointerEvents: "none" } });
19401
+ }
19395
19402
  function MobileScanningView({
19396
19403
  previewUrl,
19397
19404
  productImage,
@@ -20255,6 +20262,8 @@ function SectionDetailView({
20255
20262
  showLines,
20256
20263
  onToggleLines,
20257
20264
  overlayNode,
20265
+ renderOverlayWithFit,
20266
+ onActiveFitChange,
20258
20267
  onImageLoad,
20259
20268
  onTryOn,
20260
20269
  tryOnProcessing,
@@ -20525,6 +20534,15 @@ function SectionDetailView({
20525
20534
  };
20526
20535
  });
20527
20536
  }, [sectionResult, lengthEntry, userMeasurements, renderRaw, allSizes, displaySize, selectedLength]);
20537
+ reactExports.useEffect(() => {
20538
+ if (!onActiveFitChange) return;
20539
+ onActiveFitChange(fitRows.map((r2) => ({
20540
+ area: r2.area,
20541
+ userNum: r2.userNum ?? 0,
20542
+ chartLabel: r2.chartLabel ?? "",
20543
+ fit: r2.fit ?? "good"
20544
+ })));
20545
+ }, [fitRows, onActiveFitChange]);
20528
20546
  const goodCount = fitRows.filter(
20529
20547
  (r2) => r2.fit === "good" || r2.fit === "a-bit-tight" || r2.fit === "a-bit-loose"
20530
20548
  ).length;
@@ -20669,7 +20687,12 @@ function SectionDetailView({
20669
20687
  onLoad: onImageLoad
20670
20688
  }
20671
20689
  ),
20672
- showLines && overlayNode,
20690
+ showLines && (renderOverlayWithFit ? renderOverlayWithFit(fitRows.map((r2) => ({
20691
+ area: r2.area,
20692
+ userNum: r2.userNum ?? 0,
20693
+ chartLabel: r2.chartLabel ?? "",
20694
+ fit: r2.fit ?? "good"
20695
+ }))) : overlayNode),
20673
20696
  tryOnProcessing && tryOnStartedAt != null && /* @__PURE__ */ jsxRuntimeExports.jsx(TryOnGenerationBadge, { tryOnStartedAt, t: t2 }),
20674
20697
  isTryOnImage && onToggleLines && /* @__PURE__ */ jsxRuntimeExports.jsxs(
20675
20698
  "button",
@@ -21314,7 +21337,17 @@ function SizeResultView({
21314
21337
  const [selectedSize, setSelectedSize] = reactExports.useState(null);
21315
21338
  const [showFullChart, setShowFullChart] = reactExports.useState(false);
21316
21339
  const [showLines, setShowLines] = reactExports.useState(false);
21340
+ const [activeFitRows, setActiveFitRows] = reactExports.useState([]);
21317
21341
  const [showVisualFit, setShowVisualFit] = reactExports.useState(false);
21342
+ const [zoomImageUrl, setZoomImageUrl] = reactExports.useState(null);
21343
+ reactExports.useEffect(() => {
21344
+ if (!zoomImageUrl) return;
21345
+ const onKey = (e) => {
21346
+ if (e.key === "Escape") setZoomImageUrl(null);
21347
+ };
21348
+ window.addEventListener("keydown", onKey);
21349
+ return () => window.removeEventListener("keydown", onKey);
21350
+ }, [zoomImageUrl]);
21318
21351
  const [poseLines, setPoseLines] = reactExports.useState(null);
21319
21352
  const [poseReady, setPoseReady] = reactExports.useState(false);
21320
21353
  const [imgDims, setImgDims] = reactExports.useState({ w: 800, h: 1200 });
@@ -21526,6 +21559,69 @@ function SizeResultView({
21526
21559
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { style: { fontSize: isMobile ? "12px" : "0.62vw", lineHeight: 1.45, color: "var(--ps-text-secondary)" }, children: t2("We noticed your entered details didn't match your photo. To give you accurate sizing, we measured your body directly from the photo.") })
21527
21560
  ] }) : null;
21528
21561
  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-tryon-sr", children: [
21562
+ zoomImageUrl && typeof document !== "undefined" && reactDomExports.createPortal(
21563
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
21564
+ "div",
21565
+ {
21566
+ onClick: () => setZoomImageUrl(null),
21567
+ style: {
21568
+ position: "fixed",
21569
+ inset: 0,
21570
+ zIndex: 2147483647,
21571
+ background: "rgba(0,0,0,0.92)",
21572
+ display: "flex",
21573
+ alignItems: "center",
21574
+ justifyContent: "center",
21575
+ cursor: "zoom-out"
21576
+ },
21577
+ children: [
21578
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
21579
+ "img",
21580
+ {
21581
+ src: zoomImageUrl,
21582
+ alt: productTitle,
21583
+ style: {
21584
+ maxWidth: "100vw",
21585
+ maxHeight: "100vh",
21586
+ width: "auto",
21587
+ height: "auto",
21588
+ objectFit: "contain",
21589
+ display: "block"
21590
+ },
21591
+ onClick: (e) => e.stopPropagation()
21592
+ }
21593
+ ),
21594
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
21595
+ "button",
21596
+ {
21597
+ type: "button",
21598
+ onClick: () => setZoomImageUrl(null),
21599
+ "aria-label": "Close",
21600
+ style: {
21601
+ position: "absolute",
21602
+ top: "1rem",
21603
+ right: "1rem",
21604
+ background: "rgba(255,255,255,0.16)",
21605
+ border: "1px solid rgba(255,255,255,0.32)",
21606
+ color: "white",
21607
+ width: "2.4rem",
21608
+ height: "2.4rem",
21609
+ borderRadius: "50%",
21610
+ fontSize: "1.4rem",
21611
+ lineHeight: 1,
21612
+ cursor: "pointer",
21613
+ display: "flex",
21614
+ alignItems: "center",
21615
+ justifyContent: "center"
21616
+ },
21617
+ children: "×"
21618
+ }
21619
+ )
21620
+ ]
21621
+ }
21622
+ ),
21623
+ document.body
21624
+ ),
21529
21625
  isMobile && isSizingOnly && /* @__PURE__ */ jsxRuntimeExports.jsx(
21530
21626
  MobileScanningView,
21531
21627
  {
@@ -21660,7 +21756,9 @@ function SizeResultView({
21660
21756
  src: tryOnProcessing && previewUrl ? previewUrl : resultImageUrl || productImage,
21661
21757
  alt: productTitle,
21662
21758
  className: "ps-tryon-v2-bg-img",
21663
- onLoad: handleImgLoad
21759
+ onLoad: handleImgLoad,
21760
+ style: resultImageUrl && !tryOnProcessing ? { cursor: "zoom-in" } : void 0,
21761
+ onClick: resultImageUrl && !tryOnProcessing ? () => setZoomImageUrl(resultImageUrl) : void 0
21664
21762
  }
21665
21763
  ),
21666
21764
  tryOnProcessing && /* @__PURE__ */ jsxRuntimeExports.jsx(TryOnGenerationBadge, { tryOnStartedAt: tryOnStartedAt ?? null, t: t2 }),
@@ -21759,7 +21857,17 @@ function SizeResultView({
21759
21857
  /* ── Desktop section picker: split layout — image left, image cards right ── */
21760
21858
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-tryon-v2", children: [
21761
21859
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-tryon-v2-bg", style: { position: "relative" }, children: [
21762
- /* @__PURE__ */ jsxRuntimeExports.jsx("img", { src: tryOnProcessing && previewUrl ? previewUrl : resultImageUrl || productImage, alt: productTitle, className: "ps-tryon-v2-bg-img", onLoad: handleImgLoad }),
21860
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
21861
+ "img",
21862
+ {
21863
+ src: tryOnProcessing && previewUrl ? previewUrl : resultImageUrl || productImage,
21864
+ alt: productTitle,
21865
+ className: "ps-tryon-v2-bg-img",
21866
+ onLoad: handleImgLoad,
21867
+ style: resultImageUrl && !tryOnProcessing ? { cursor: "zoom-in" } : void 0,
21868
+ onClick: resultImageUrl && !tryOnProcessing ? () => setZoomImageUrl(resultImageUrl) : void 0
21869
+ }
21870
+ ),
21763
21871
  tryOnProcessing && /* @__PURE__ */ jsxRuntimeExports.jsx(TryOnGenerationBadge, { tryOnStartedAt: tryOnStartedAt ?? null, t: t2 }),
21764
21872
  /* @__PURE__ */ jsxRuntimeExports.jsx(RegenScanOverlay, { active: !!tryOnProcessing }),
21765
21873
  resultImageUrl && !tryOnProcessing && poseReady && poseLines && /* @__PURE__ */ jsxRuntimeExports.jsx(MeasurementOverlay, { lines: poseLines, fitRows: (() => {
@@ -21932,6 +22040,7 @@ function SizeResultView({
21932
22040
  isMobile: true,
21933
22041
  renderRaw: isAccessory,
21934
22042
  allSizes: sizingResult?.allSizes,
22043
+ onActiveFitChange: setActiveFitRows,
21935
22044
  isTryOnImage: !!resultImageUrl,
21936
22045
  showLines,
21937
22046
  onToggleLines: isAccessory ? void 0 : () => setShowLines(!showLines),
@@ -21959,12 +22068,23 @@ function SizeResultView({
21959
22068
  src: tryOnProcessing && previewUrl ? previewUrl : resultImageUrl || productImage,
21960
22069
  alt: productTitle,
21961
22070
  className: "ps-tryon-v2-bg-img",
21962
- onLoad: handleImgLoad
22071
+ onLoad: handleImgLoad,
22072
+ style: resultImageUrl && !tryOnProcessing ? { cursor: "zoom-in" } : void 0,
22073
+ onClick: resultImageUrl && !tryOnProcessing ? () => setZoomImageUrl(resultImageUrl) : void 0
21963
22074
  }
21964
22075
  ),
21965
22076
  tryOnProcessing && /* @__PURE__ */ jsxRuntimeExports.jsx(TryOnGenerationBadge, { tryOnStartedAt: tryOnStartedAt ?? null, t: t2 }),
21966
22077
  /* @__PURE__ */ jsxRuntimeExports.jsx(RegenScanOverlay, { active: !!tryOnProcessing }),
21967
- resultImageUrl && !tryOnProcessing && poseReady && poseLines && /* @__PURE__ */ jsxRuntimeExports.jsx(MeasurementOverlay, { lines: poseLines, fitRows: (sizingResult?.matchDetails || []).map((m2) => ({ area: m2.measurement, userNum: parseFloat(m2.userValue) || 0, chartLabel: m2.chartRange || "", fit: m2.fit })), show: showLines, imgWidth: imgDims.w, imgHeight: imgDims.h }),
22078
+ resultImageUrl && !tryOnProcessing && poseReady && poseLines && /* @__PURE__ */ jsxRuntimeExports.jsx(
22079
+ MeasurementOverlay,
22080
+ {
22081
+ lines: poseLines,
22082
+ fitRows: activeFitRows.length > 0 ? activeFitRows : (sizingResult?.matchDetails || []).map((m2) => ({ area: m2.measurement, userNum: parseFloat(m2.userValue) || 0, chartLabel: m2.chartRange || "", fit: m2.fit })),
22083
+ show: showLines,
22084
+ imgWidth: imgDims.w,
22085
+ imgHeight: imgDims.h
22086
+ }
22087
+ ),
21968
22088
  resultImageUrl && !tryOnProcessing && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { style: { position: "absolute", bottom: "0.5vw", left: "0.5vw", zIndex: 3, display: "flex", flexDirection: "column", gap: "0.3vw" }, children: [
21969
22089
  !isAccessory && /* @__PURE__ */ jsxRuntimeExports.jsx("button", { className: "ps-tryon-sr-glass-btn", onClick: () => setShowLines(!showLines), children: showLines ? t2("Hide Fit") : t2("Show Fit") }),
21970
22090
  /* @__PURE__ */ jsxRuntimeExports.jsx("button", { className: "ps-tryon-sr-glass-btn", onClick: handleDownload, children: t2("Download") })
@@ -22001,7 +22121,8 @@ function SizeResultView({
22001
22121
  tryOnStartedAt,
22002
22122
  t: t2,
22003
22123
  renderRaw: isAccessory,
22004
- allSizes: sizingResult?.allSizes
22124
+ allSizes: sizingResult?.allSizes,
22125
+ onActiveFitChange: setActiveFitRows
22005
22126
  }
22006
22127
  )
22007
22128
  ) : sizingResult?.found === false ? (
@@ -23272,7 +23393,7 @@ function CreateProfileWizard({ onSave, onCancel, apiUrl, apiKey, onPhotoPreview,
23272
23393
  setPhotoStatus(t2("Analyzing photo…"));
23273
23394
  try {
23274
23395
  const ageCheckPromise = apiUrl && apiKey ? checkAgeBeforeUpload(file, apiUrl, apiKey) : Promise.resolve({ isAdult: true, confidence: "low" });
23275
- const compressPromise = compressImage(file);
23396
+ const compressPromise = compressImage(file, { maxDimension: 1280, quality: 0.92 });
23276
23397
  const ageResult = await ageCheckPromise;
23277
23398
  if (!ageResult.isAdult) {
23278
23399
  const reason = ageResult.reasoning?.trim() || t2("This photo appears to be of a minor. Please upload a photo of someone 18 or older.");
@@ -23520,6 +23641,7 @@ function CreateProfileWizard({ onSave, onCancel, apiUrl, apiKey, onPhotoPreview,
23520
23641
  setEstimating(true);
23521
23642
  const MIN_HOLD_MS = 6e3;
23522
23643
  const minHold = new Promise((r2) => setTimeout(r2, MIN_HOLD_MS));
23644
+ let liveEstimates = null;
23523
23645
  if (onEstimate && photoBase64) {
23524
23646
  const heightRaw = unit === "in" ? (parseInt(heightFt, 10) || 0) * 12 + (parseInt(heightInch, 10) || 0) : parseFloat(heightVal);
23525
23647
  try {
@@ -23533,6 +23655,7 @@ function CreateProfileWizard({ onSave, onCancel, apiUrl, apiKey, onPhotoPreview,
23533
23655
  ...a > 0 ? { age: a } : {}
23534
23656
  });
23535
23657
  if (result?.estimates) {
23658
+ liveEstimates = result.estimates;
23536
23659
  setEstimateResults(result.estimates);
23537
23660
  }
23538
23661
  } catch {
@@ -23540,6 +23663,12 @@ function CreateProfileWizard({ onSave, onCancel, apiUrl, apiKey, onPhotoPreview,
23540
23663
  }
23541
23664
  await minHold;
23542
23665
  setEstimating(false);
23666
+ const payload2 = buildImagePayload();
23667
+ if (liveEstimates) {
23668
+ payload2.measurements = liveEstimates;
23669
+ payload2.measurementsUnit = "cm";
23670
+ }
23671
+ onSave(payload2);
23543
23672
  return;
23544
23673
  }
23545
23674
  if (imageStep === "details") {
@@ -24530,20 +24659,6 @@ function CreateProfileWizard({ onSave, onCancel, apiUrl, apiKey, onPhotoPreview,
24530
24659
  { title: t2("FINALIZING RESULT"), desc: t2("Almost done — preparing your recommendation.") }
24531
24660
  ];
24532
24661
  const stage = stages[Math.min(scanStageIdx, stages.length - 1)] ?? stages[0];
24533
- const SKELETON_CONNECTIONS2 = [
24534
- ["leftShoulder", "rightShoulder"],
24535
- ["leftShoulder", "leftElbow"],
24536
- ["leftElbow", "leftWrist"],
24537
- ["rightShoulder", "rightElbow"],
24538
- ["rightElbow", "rightWrist"],
24539
- ["leftShoulder", "leftHip"],
24540
- ["rightShoulder", "rightHip"],
24541
- ["leftHip", "rightHip"],
24542
- ["leftHip", "leftKnee"],
24543
- ["leftKnee", "leftAnkle"],
24544
- ["rightHip", "rightKnee"],
24545
- ["rightKnee", "rightAnkle"]
24546
- ];
24547
24662
  return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-cpw-fade-in", style: { display: "flex", flexDirection: "column", flex: 1, overflow: "auto" }, children: estimating ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { style: { display: "flex", gap: "1.2vw", padding: "1.5vw", flex: 1 }, children: [
24548
24663
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { style: {
24549
24664
  flex: 1,
@@ -24572,40 +24687,12 @@ function CreateProfileWizard({ onSave, onCancel, apiUrl, apiKey, onPhotoPreview,
24572
24687
  }
24573
24688
  }
24574
24689
  ),
24575
- scanLandmarks && /* @__PURE__ */ jsxRuntimeExports.jsxs(
24576
- "svg",
24690
+ scanLandmarks && /* @__PURE__ */ jsxRuntimeExports.jsx(
24691
+ PoseOverlay,
24577
24692
  {
24578
- viewBox: `0 0 ${scanImgDims.w} ${scanImgDims.h}`,
24579
- preserveAspectRatio: "xMidYMid meet",
24580
- style: { position: "absolute", inset: 0, width: "100%", height: "100%", pointerEvents: "none" },
24581
- "aria-hidden": "true",
24582
- children: [
24583
- SKELETON_CONNECTIONS2.map(([a, b], i) => {
24584
- const pa2 = scanLandmarks[a];
24585
- const pb2 = scanLandmarks[b];
24586
- if (!pa2 || !pb2) return null;
24587
- return /* @__PURE__ */ jsxRuntimeExports.jsx(
24588
- "line",
24589
- {
24590
- x1: pa2.x * scanImgDims.w,
24591
- y1: pa2.y * scanImgDims.h,
24592
- x2: pb2.x * scanImgDims.w,
24593
- y2: pb2.y * scanImgDims.h,
24594
- stroke: "rgba(100,210,255,0.95)",
24595
- strokeWidth: Math.max(3, scanImgDims.w / 180),
24596
- strokeLinecap: "round"
24597
- },
24598
- `l-${i}`
24599
- );
24600
- }),
24601
- Object.entries(scanLandmarks).map(([key, v2]) => {
24602
- const p2 = v2;
24603
- return /* @__PURE__ */ jsxRuntimeExports.jsxs("g", { children: [
24604
- /* @__PURE__ */ jsxRuntimeExports.jsx("circle", { cx: p2.x * scanImgDims.w, cy: p2.y * scanImgDims.h, r: Math.max(7, scanImgDims.w / 75), fill: "rgba(100,210,255,0.25)" }),
24605
- /* @__PURE__ */ jsxRuntimeExports.jsx("circle", { cx: p2.x * scanImgDims.w, cy: p2.y * scanImgDims.h, r: Math.max(4, scanImgDims.w / 120), fill: "rgba(100,210,255,0.95)" })
24606
- ] }, key);
24607
- })
24608
- ]
24693
+ landmarks: scanLandmarks,
24694
+ width: scanImgDims.w,
24695
+ height: scanImgDims.h
24609
24696
  }
24610
24697
  )
24611
24698
  ] }),
@@ -28434,6 +28521,30 @@ function PrimeStyleTryonInner({
28434
28521
  sizeGuideData
28435
28522
  }) {
28436
28523
  const effectiveProductId = productId || productImage;
28524
+ const renderCountRef = reactExports.useRef(0);
28525
+ const mountIdRef = reactExports.useRef(null);
28526
+ if (mountIdRef.current === null && typeof window !== "undefined") {
28527
+ const w2 = window;
28528
+ w2.__psSdkDiag = w2.__psSdkDiag || { mounts: 0, effectFires: 0, tryonMounts: 0, tryonRenders: 0 };
28529
+ w2.__psSdkDiag.tryonMounts += 1;
28530
+ mountIdRef.current = w2.__psSdkDiag.tryonMounts;
28531
+ }
28532
+ renderCountRef.current += 1;
28533
+ if (typeof window !== "undefined") {
28534
+ window.__psSdkDiag.tryonRenders = (window.__psSdkDiag.tryonRenders || 0) + 1;
28535
+ }
28536
+ console.log(
28537
+ `[ps-sdk:tryon#${mountIdRef.current}] render #${renderCountRef.current}`,
28538
+ {
28539
+ productId: effectiveProductId,
28540
+ productImagesLen: productImages?.length ?? 0,
28541
+ productImagesRef: productImages
28542
+ }
28543
+ );
28544
+ reactExports.useEffect(() => {
28545
+ console.log(`[ps-sdk:tryon#${mountIdRef.current}] MOUNT`);
28546
+ return () => console.log(`[ps-sdk:tryon#${mountIdRef.current}] UNMOUNT`);
28547
+ }, []);
28437
28548
  const [activeLocale, setActiveLocale] = reactExports.useState(() => locale || "");
28438
28549
  reactExports.useEffect(() => {
28439
28550
  if (locale !== void 0) setActiveLocale(locale);
@@ -28562,15 +28673,31 @@ function PrimeStyleTryonInner({
28562
28673
  if (pollingRef.current) clearInterval(pollingRef.current);
28563
28674
  };
28564
28675
  }, [apiUrl]);
28676
+ const pickFireCountRef = reactExports.useRef(0);
28565
28677
  reactExports.useEffect(() => {
28678
+ pickFireCountRef.current += 1;
28679
+ const fireN = pickFireCountRef.current;
28680
+ const reason = !productImages ? "no productImages" : productImages.length < 2 ? `productImages.length=${productImages.length}` : `len=${productImages.length} firstUrl=${productImages[0]?.slice(0, 40)}`;
28681
+ console.log(`[ps-sdk:pick] effect FIRE #${fireN} — ${reason}`, {
28682
+ productImagesRef: productImages,
28683
+ garmentReferenceImageSet: !!garmentReferenceImage,
28684
+ apiUrl,
28685
+ productTitle
28686
+ });
28566
28687
  bestGarmentImageRef.current = null;
28567
28688
  if (garmentReferenceImage) {
28568
28689
  bestGarmentImageRef.current = garmentReferenceImage;
28690
+ console.log(`[ps-sdk:pick] #${fireN} early-return — using prop override`);
28691
+ return;
28692
+ }
28693
+ if (!productImages || productImages.length < 2) {
28694
+ console.log(`[ps-sdk:pick] #${fireN} early-return — too few images, no /pick-best call`);
28569
28695
  return;
28570
28696
  }
28571
- if (!productImages || productImages.length < 2) return;
28572
28697
  const baseUrl = getApiUrl(apiUrl);
28573
28698
  const ctrl = new AbortController();
28699
+ const t0 = Date.now();
28700
+ console.log(`[ps-sdk:pick] #${fireN} → POST /api/catalog/pick-best-garment-image`);
28574
28701
  fetch(`${baseUrl}/api/catalog/pick-best-garment-image`, {
28575
28702
  method: "POST",
28576
28703
  headers: { "Content-Type": "application/json" },
@@ -28579,11 +28706,17 @@ function PrimeStyleTryonInner({
28579
28706
  }).then((r2) => r2.ok ? r2.json() : null).then((j) => {
28580
28707
  if (j?.bestUrl) {
28581
28708
  bestGarmentImageRef.current = j.bestUrl;
28582
- console.log(`[ps-sdk] pre-picked garment reference: ${j.bestUrl}`);
28709
+ console.log(`[ps-sdk:pick] #${fireN} ← OK in ${Date.now() - t0}ms ${j.bestUrl}`);
28710
+ } else {
28711
+ console.log(`[ps-sdk:pick] #${fireN} ← no bestUrl in response (${Date.now() - t0}ms)`);
28583
28712
  }
28584
- }).catch(() => {
28713
+ }).catch((e) => {
28714
+ console.log(`[ps-sdk:pick] #${fireN} ← aborted/network error in ${Date.now() - t0}ms`, e?.name || e);
28585
28715
  });
28586
- return () => ctrl.abort();
28716
+ return () => {
28717
+ ctrl.abort();
28718
+ console.log(`[ps-sdk:pick] #${fireN} cleanup — aborted in-flight fetch`);
28719
+ };
28587
28720
  }, [productImages, garmentReferenceImage, apiUrl, productTitle]);
28588
28721
  const TARGET_SECONDS2 = 22;
28589
28722
  const progressRef = reactExports.useRef(0);
@@ -29713,12 +29846,12 @@ function PrimeStyleTryonInner({
29713
29846
  if (noFitFoundRef.current) return;
29714
29847
  if (autoTryOnFiredRef.current) return;
29715
29848
  if (tryOnProcessing || resultImageUrl) return;
29716
- if (!sizingResult) return;
29849
+ if (!sizingLoading && !sizingResult) return;
29717
29850
  const file = selectedFile || selectedFileRef.current;
29718
29851
  if (!file) return;
29719
29852
  autoTryOnFiredRef.current = true;
29720
29853
  handleTryOnSubmit();
29721
- }, [view, selectedFile, sizingResult, tryOnProcessing, resultImageUrl, handleTryOnSubmit]);
29854
+ }, [view, selectedFile, sizingLoading, sizingResult, tryOnProcessing, resultImageUrl, handleTryOnSubmit]);
29722
29855
  const handleDownload = reactExports.useCallback(() => {
29723
29856
  if (!resultImageUrl) return;
29724
29857
  if (resultImageUrl.startsWith("data:")) {
@@ -30353,7 +30486,12 @@ function PrimeStyleTryonInner({
30353
30486
  const heightUnitVal = newProfile.sizingUnit === "in" ? "in" : "cm";
30354
30487
  const weightUnitVal = newProfile.sizingUnit === "in" ? "lbs" : "kg";
30355
30488
  const photoBase64 = newProfile.photoBase64;
30356
- const hasMeasurements = !!newProfile.measurements && Object.keys(newProfile.measurements).length > 0;
30489
+ const measurementsObj = newProfile.measurements;
30490
+ const hasMeasurements = !!measurementsObj && Object.keys(measurementsObj).length > 0 && typeof measurementsObj.neckCircumference === "number" && measurementsObj.neckCircumference > 0;
30491
+ console.log(
30492
+ "[ps-sdk:saveProfile] hasMeasurements=" + hasMeasurements,
30493
+ measurementsObj ? { neck: measurementsObj.neckCircumference, chest: measurementsObj.chest, photoBase64Set: !!photoBase64 } : null
30494
+ );
30357
30495
  if (!hasMeasurements && (photoBase64 || heightVal > 0 && weightVal > 0)) {
30358
30496
  setEstimatingProfileIds((prev) => new Set(prev).add(newProfile.id));
30359
30497
  const landmarksPromise = photoBase64 ? detectBodyLandmarks(photoBase64).catch(() => null) : Promise.resolve(null);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@primestyleai/tryon",
3
- "version": "5.10.171",
3
+ "version": "5.10.172",
4
4
  "description": "PrimeStyle Virtual Try-On SDK — React component & Web Component",
5
5
  "type": "module",
6
6
  "main": "dist/primestyle-tryon.js",
@@ -64,5 +64,8 @@
64
64
  "terser": "^5.31.0",
65
65
  "typescript": "^5.5.0",
66
66
  "vite": "^5.4.0"
67
+ },
68
+ "dependencies": {
69
+ "@primestyleai/tryon": "file:primestyleai-tryon-5.10.171.tgz"
67
70
  }
68
71
  }