@primestyleai/tryon 5.6.9 → 5.6.11

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.
@@ -4002,18 +4002,21 @@ const STYLES = `
4002
4002
  .ps-msp-avatar { color: var(--ps-accent); border-color: var(--ps-accent); }
4003
4003
  .ps-msp-avatar-tag { background: var(--ps-accent); }
4004
4004
 
4005
- .ps-msp-card-thumb {
4006
- height: 90px;
4005
+ /* Big circular profile avatar — only chrome on the card, no
4006
+ rectangular hero. Shows the head crop via object-position 12%. */
4007
+ .ps-msp-card-circle {
4008
+ width: 110px; height: 110px;
4009
+ border-radius: 50%;
4007
4010
  background: var(--ps-bg-secondary);
4008
- border-radius: 8px;
4009
- display: flex; align-items: center; justify-content: center;
4010
- margin-bottom: 12px;
4011
- color: var(--ps-text-secondary);
4011
+ border: 3px solid var(--ps-accent);
4012
+ box-shadow: 0 6px 18px rgba(33, 84, 239, 0.18);
4012
4013
  overflow: hidden;
4014
+ display: flex; align-items: center; justify-content: center;
4015
+ color: var(--ps-accent);
4016
+ margin: 6px auto 14px;
4013
4017
  }
4014
- .ps-msp-card-photo {
4015
- width: 100%; height: 100%; object-fit: cover; display: block;
4016
- /* Same head-bias crop as the profile detail avatar */
4018
+ .ps-msp-card-circle img {
4019
+ width: 100%; height: 100%; object-fit: cover;
4017
4020
  object-position: 50% 12%;
4018
4021
  }
4019
4022
  .ps-msp-avatar {
@@ -4656,7 +4659,32 @@ const STYLES = `
4656
4659
  100% { background-position: -200% 0; }
4657
4660
  }
4658
4661
 
4659
- /* Saved sizes history (per-product cache) */
4662
+ /* Inline measurement edit inputs */
4663
+ .ps-pmv-measure.ps-editing {
4664
+ border-color: var(--ps-accent);
4665
+ background: rgba(33, 84, 239, 0.04);
4666
+ }
4667
+ .ps-pmv-measure-edit {
4668
+ display: flex; align-items: baseline; gap: 4px;
4669
+ }
4670
+ .ps-pmv-measure-input {
4671
+ flex: 1; min-width: 0;
4672
+ background: transparent; border: none; outline: none;
4673
+ font-family: inherit;
4674
+ font-size: 18px; font-weight: 700;
4675
+ color: var(--ps-text-primary);
4676
+ padding: 0;
4677
+ font-feature-settings: "tnum" 1;
4678
+ }
4679
+ .ps-pmv-measure-input::-webkit-outer-spin-button,
4680
+ .ps-pmv-measure-input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
4681
+ .ps-pmv-measure-input[type="number"] { -moz-appearance: textfield; }
4682
+ .ps-pmv-measure-input-unit {
4683
+ font-size: 11px; color: var(--ps-text-muted); font-weight: 500;
4684
+ }
4685
+
4686
+ /* Saved sizes history (per-product cache) — kept for backwards compat,
4687
+ no longer rendered inside ProfileMeasurementsView */
4660
4688
  .ps-pmv-history {
4661
4689
  display: flex; flex-direction: column; gap: 8px;
4662
4690
  }
@@ -6055,17 +6083,25 @@ function MobileSkeleton({ landmarks, w, h }) {
6055
6083
  }
6056
6084
  function MobileScanningView({
6057
6085
  previewUrl,
6086
+ productImage,
6058
6087
  bodyLandmarks,
6059
6088
  sizingDone,
6060
6089
  onSwitchToManual,
6061
6090
  t
6062
6091
  }) {
6063
- const stages = [
6092
+ const displayImage = previewUrl || productImage || "";
6093
+ const isPhotoMode = !!previewUrl;
6094
+ const stages = isPhotoMode ? [
6064
6095
  { title: t("DETECTING POSE"), desc: t("Identifying body landmarks from your photo."), viewfinderText: t("DETECTING POSE") },
6065
6096
  { title: t("SCANNING FRAME"), desc: t("Our AI is mapping your proportions to calculate the perfect fit."), viewfinderText: t("SCANNING FRAME") },
6066
6097
  { title: t("ANALYZING BODY"), desc: t("Measuring shoulders, chest, waist and hips."), viewfinderText: t("ANALYZING") },
6067
6098
  { title: t("MATCHING SIZE"), desc: t("Comparing your measurements to the size guide."), viewfinderText: t("MATCHING SIZE") },
6068
6099
  { title: t("FINALIZING RESULT"), desc: t("Almost done — preparing your recommendation."), viewfinderText: t("FINALIZING") }
6100
+ ] : [
6101
+ { title: t("READING YOUR PROFILE"), desc: t("Loading your saved measurements and body shape answers."), viewfinderText: t("READING PROFILE") },
6102
+ { title: t("ESTIMATING BODY"), desc: t("Computing chest, waist, hips, sleeve and inseam from your basics."), viewfinderText: t("ESTIMATING BODY") },
6103
+ { title: t("MATCHING SIZE"), desc: t("Comparing your measurements to the garment's size guide."), viewfinderText: t("MATCHING SIZE") },
6104
+ { title: t("FINALIZING RESULT"), desc: t("Almost done — preparing your recommendation."), viewfinderText: t("FINALIZING") }
6069
6105
  ];
6070
6106
  const [dims, setDims] = useState({ w: 800, h: 1200 });
6071
6107
  const handleImgLoad = (e) => {
@@ -6075,7 +6111,7 @@ function MobileScanningView({
6075
6111
  const [stageIdx, setStageIdx] = useState(0);
6076
6112
  useEffect(() => {
6077
6113
  if (sizingDone) return;
6078
- if (bodyLandmarks && stageIdx === 0) {
6114
+ if (isPhotoMode && bodyLandmarks && stageIdx === 0) {
6079
6115
  setStageIdx(1);
6080
6116
  return;
6081
6117
  }
@@ -6083,7 +6119,7 @@ function MobileScanningView({
6083
6119
  setStageIdx((i) => Math.min(i + 1, stages.length - 1));
6084
6120
  }, 1600);
6085
6121
  return () => clearInterval(id);
6086
- }, [bodyLandmarks, sizingDone, stageIdx, stages.length]);
6122
+ }, [bodyLandmarks, sizingDone, stageIdx, stages.length, isPhotoMode]);
6087
6123
  const current = stages[stageIdx] ?? stages[0];
6088
6124
  const [feedTick, setFeedTick] = useState(1);
6089
6125
  useEffect(() => {
@@ -6098,10 +6134,10 @@ function MobileScanningView({
6098
6134
  "LIVE_FEED:",
6099
6135
  String(feedTick).padStart(2, "0")
6100
6136
  ] }),
6101
- /* @__PURE__ */ jsx(
6137
+ displayImage && /* @__PURE__ */ jsx(
6102
6138
  "img",
6103
6139
  {
6104
- src: previewUrl,
6140
+ src: displayImage,
6105
6141
  alt: t("Your photo"),
6106
6142
  className: "ps-msc-photo",
6107
6143
  onLoad: handleImgLoad
@@ -6114,7 +6150,7 @@ function MobileScanningView({
6114
6150
  /* @__PURE__ */ jsx("span", { className: "ps-msc-corner ps-br" })
6115
6151
  ] }),
6116
6152
  /* @__PURE__ */ jsx("div", { className: "ps-msc-scanline" }),
6117
- bodyLandmarks && /* @__PURE__ */ jsx("div", { className: "ps-msc-pose-wrap", children: /* @__PURE__ */ jsx(MobileSkeleton, { landmarks: bodyLandmarks, w: dims.w, h: dims.h }) }),
6153
+ isPhotoMode && bodyLandmarks && /* @__PURE__ */ jsx("div", { className: "ps-msc-pose-wrap", children: /* @__PURE__ */ jsx(MobileSkeleton, { landmarks: bodyLandmarks, w: dims.w, h: dims.h }) }),
6118
6154
  /* @__PURE__ */ jsxs("div", { className: "ps-msc-vf-bottom", children: [
6119
6155
  /* @__PURE__ */ jsxs("div", { className: "ps-msc-vf-text", children: [
6120
6156
  current.viewfinderText,
@@ -7136,7 +7172,17 @@ function SizeResultView({
7136
7172
  previewUrl: !!previewUrl
7137
7173
  });
7138
7174
  return /* @__PURE__ */ jsxs("div", { className: "ps-tryon-sr", children: [
7139
- isSizingOnly && /* @__PURE__ */ jsxs("div", { className: "ps-tryon-sr-split", children: [
7175
+ isMobile && isSizingOnly && /* @__PURE__ */ jsx(
7176
+ MobileScanningView,
7177
+ {
7178
+ productImage,
7179
+ bodyLandmarks: null,
7180
+ sizingDone,
7181
+ onSwitchToManual: () => setView("body-profile"),
7182
+ t
7183
+ }
7184
+ ),
7185
+ !isMobile && isSizingOnly && /* @__PURE__ */ jsxs("div", { className: "ps-tryon-sr-split", children: [
7140
7186
  /* @__PURE__ */ jsx("div", { className: "ps-tryon-sr-img-col", children: /* @__PURE__ */ jsx("img", { src: productImage, alt: productTitle, className: "ps-tryon-sr-product-img" }) }),
7141
7187
  /* @__PURE__ */ jsxs("div", { className: "ps-tryon-sr-right-col", style: { display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center", gap: "0.8vw" }, children: [
7142
7188
  /* @__PURE__ */ jsx("div", { className: "ps-tryon-size-loading-spinner" }),
@@ -8207,7 +8253,7 @@ function ProfileMeasurementsView({
8207
8253
  profile,
8208
8254
  isActive,
8209
8255
  onSelect,
8210
- onEdit,
8256
+ onSaveMeasurements,
8211
8257
  onDelete,
8212
8258
  onBack,
8213
8259
  onSave,
@@ -8217,6 +8263,33 @@ function ProfileMeasurementsView({
8217
8263
  const measurements = profile.measurements || {};
8218
8264
  const unit = profile.measurementsUnit || "cm";
8219
8265
  const hasMeasurements = Object.keys(measurements).some((k) => measurements[k] != null);
8266
+ const [editing, setEditing] = useState(false);
8267
+ const [draft, setDraft] = useState({});
8268
+ useEffect(() => {
8269
+ if (!editing) return;
8270
+ const next = {};
8271
+ for (const f of fields) {
8272
+ const v = measurements[f.key];
8273
+ if (v != null) next[f.key] = String(Math.round(v * 10) / 10);
8274
+ }
8275
+ setDraft(next);
8276
+ }, [editing]);
8277
+ const handleDraftChange = (key, value) => {
8278
+ setDraft((prev) => ({ ...prev, [key]: value }));
8279
+ };
8280
+ const handleEditSave = () => {
8281
+ const updated = { ...measurements };
8282
+ for (const [key, raw] of Object.entries(draft)) {
8283
+ const v = parseFloat(raw);
8284
+ if (!isNaN(v) && v > 0) updated[key] = v;
8285
+ }
8286
+ onSaveMeasurements(updated);
8287
+ setEditing(false);
8288
+ };
8289
+ const handleEditCancel = () => {
8290
+ setEditing(false);
8291
+ setDraft({});
8292
+ };
8220
8293
  const heightDisplay = (() => {
8221
8294
  const h = profile.height ?? profile.heightCm;
8222
8295
  if (!h) return "—";
@@ -8267,44 +8340,45 @@ function ProfileMeasurementsView({
8267
8340
  /* @__PURE__ */ jsx("span", { children: t("Calculating...") })
8268
8341
  ] })
8269
8342
  ] }),
8270
- /* @__PURE__ */ jsx("div", { className: "ps-pmv-section-sub", children: hasMeasurements ? t("Calculated from your basics. Used to recommend the perfect size for any product.") : t("Our AI is computing your full body proportions — this usually takes a few seconds.") })
8343
+ /* @__PURE__ */ jsx("div", { className: "ps-pmv-section-sub", children: hasMeasurements ? editing ? t("Tap any value to edit. Save when you're done.") : t("Calculated from your basics. Used to recommend the perfect size for any product.") : t("Our AI is computing your full body proportions — this usually takes a few seconds.") })
8271
8344
  ] }),
8272
8345
  /* @__PURE__ */ jsx("div", { className: "ps-pmv-measure-grid", children: fields.map((f) => {
8273
8346
  const v = measurements[f.key];
8274
- return /* @__PURE__ */ jsxs("div", { className: `ps-pmv-measure${v == null ? " ps-loading" : ""}`, children: [
8347
+ return /* @__PURE__ */ jsxs("div", { className: `ps-pmv-measure${v == null && !editing ? " ps-loading" : ""}${editing ? " ps-editing" : ""}`, children: [
8275
8348
  /* @__PURE__ */ jsx("div", { className: "ps-pmv-measure-label", children: t(f.label) }),
8276
- /* @__PURE__ */ jsx("div", { className: `ps-pmv-measure-value${v == null ? " ps-loading" : ""}`, children: v != null ? `${Math.round(v * 10) / 10} ${unit}` : "—" })
8349
+ editing ? /* @__PURE__ */ jsxs("div", { className: "ps-pmv-measure-edit", children: [
8350
+ /* @__PURE__ */ jsx(
8351
+ "input",
8352
+ {
8353
+ type: "number",
8354
+ inputMode: "decimal",
8355
+ className: "ps-pmv-measure-input",
8356
+ value: draft[f.key] ?? "",
8357
+ placeholder: "—",
8358
+ onChange: (e) => handleDraftChange(f.key, e.target.value)
8359
+ }
8360
+ ),
8361
+ /* @__PURE__ */ jsx("span", { className: "ps-pmv-measure-input-unit", children: unit })
8362
+ ] }) : /* @__PURE__ */ jsx("div", { className: `ps-pmv-measure-value${v == null ? " ps-loading" : ""}`, children: v != null ? `${Math.round(v * 10) / 10} ${unit}` : "—" })
8277
8363
  ] }, f.key);
8278
8364
  }) })
8279
8365
  ] }),
8280
- profile.sizeHistory && profile.sizeHistory.length > 0 && /* @__PURE__ */ jsxs("div", { className: "ps-pmv-section", children: [
8281
- /* @__PURE__ */ jsxs("div", { className: "ps-pmv-section-head", children: [
8282
- /* @__PURE__ */ jsx("div", { className: "ps-pmv-section-title", children: t("SAVED SIZES") }),
8283
- /* @__PURE__ */ jsx("div", { className: "ps-pmv-section-sub", children: t("Recommendations from this profile across products") })
8284
- ] }),
8285
- /* @__PURE__ */ jsx("div", { className: "ps-pmv-history", children: profile.sizeHistory.slice(0, 6).map((entry) => /* @__PURE__ */ jsxs("div", { className: "ps-pmv-history-card", children: [
8286
- entry.productImage && /* @__PURE__ */ jsx("div", { className: "ps-pmv-history-thumb", children: /* @__PURE__ */ jsx("img", { src: entry.productImage, alt: entry.productTitle }) }),
8287
- /* @__PURE__ */ jsxs("div", { className: "ps-pmv-history-info", children: [
8288
- /* @__PURE__ */ jsx("div", { className: "ps-pmv-history-name", children: entry.productTitle }),
8289
- /* @__PURE__ */ jsx("div", { className: "ps-pmv-history-meta", children: new Date(entry.savedAt).toLocaleDateString(void 0, { month: "short", day: "numeric" }) })
8290
- ] }),
8291
- /* @__PURE__ */ jsx("div", { className: "ps-pmv-history-size", children: entry.recommendedSize })
8292
- ] }, `${entry.productId}-${entry.savedAt}`)) })
8293
- ] }),
8294
8366
  /* @__PURE__ */ jsxs("div", { className: "ps-pmv-actions", children: [
8295
- /* @__PURE__ */ jsx("button", { type: "button", className: "ps-pmv-btn-secondary", onClick: onBack, children: t("Back") }),
8367
+ /* @__PURE__ */ jsx("button", { type: "button", className: "ps-pmv-btn-secondary", onClick: editing ? handleEditCancel : onBack, children: editing ? t("Cancel") : t("Back") }),
8296
8368
  /* @__PURE__ */ jsxs("div", { className: "ps-pmv-actions-right", children: [
8297
- /* @__PURE__ */ jsx("button", { type: "button", className: "ps-pmv-btn-edit", onClick: onEdit, children: t("Edit") }),
8298
- /* @__PURE__ */ jsx("button", { type: "button", className: "ps-pmv-btn-delete", onClick: onDelete, children: t("Delete") }),
8299
- !isActive && /* @__PURE__ */ jsx("button", { type: "button", className: "ps-pmv-btn-secondary", onClick: onSelect, children: t("USE THIS PROFILE") }),
8369
+ !editing && /* @__PURE__ */ jsxs(Fragment, { children: [
8370
+ /* @__PURE__ */ jsx("button", { type: "button", className: "ps-pmv-btn-edit", onClick: () => setEditing(true), children: t("Edit") }),
8371
+ /* @__PURE__ */ jsx("button", { type: "button", className: "ps-pmv-btn-delete", onClick: onDelete, children: t("Delete") }),
8372
+ !isActive && /* @__PURE__ */ jsx("button", { type: "button", className: "ps-pmv-btn-secondary", onClick: onSelect, children: t("USE THIS PROFILE") })
8373
+ ] }),
8300
8374
  /* @__PURE__ */ jsx(
8301
8375
  "button",
8302
8376
  {
8303
8377
  type: "button",
8304
8378
  className: "ps-pmv-btn-primary",
8305
- onClick: onSave,
8379
+ onClick: editing ? handleEditSave : onSave,
8306
8380
  disabled: !hasMeasurements,
8307
- children: hasMeasurements ? t("SAVE") : /* @__PURE__ */ jsxs(Fragment, { children: [
8381
+ children: hasMeasurements ? editing ? t("SAVE CHANGES") : t("SAVE") : /* @__PURE__ */ jsxs(Fragment, { children: [
8308
8382
  /* @__PURE__ */ jsx("span", { className: "ps-pmv-btn-spinner" }),
8309
8383
  t("CALCULATING")
8310
8384
  ] })
@@ -8314,12 +8388,6 @@ function ProfileMeasurementsView({
8314
8388
  ] })
8315
8389
  ] });
8316
8390
  }
8317
- function ProfileAvatar({ gender }) {
8318
- return /* @__PURE__ */ jsxs("div", { className: "ps-msp-avatar", children: [
8319
- /* @__PURE__ */ jsx(UserIcon, { size: 28 }),
8320
- gender && /* @__PURE__ */ jsx("span", { className: `ps-msp-avatar-tag ps-${gender}`, children: gender === "female" ? "♀" : "♂" })
8321
- ] });
8322
- }
8323
8391
  function ProfileCard({
8324
8392
  profile,
8325
8393
  isActive,
@@ -8357,7 +8425,7 @@ function ProfileCard({
8357
8425
  tabIndex: 0,
8358
8426
  children: [
8359
8427
  /* @__PURE__ */ jsx("div", { className: "ps-msp-card-header", children: /* @__PURE__ */ jsx("span", { className: "ps-msp-card-tag", children: isActive ? t("DEFAULT PROFILE") : profile.gender === "female" ? t("WOMEN'S FIT") : t("MEN'S FIT") }) }),
8360
- /* @__PURE__ */ jsx("div", { className: "ps-msp-card-thumb", children: profile.photoBase64 ? /* @__PURE__ */ jsx("img", { src: profile.photoBase64, alt: profile.name, className: "ps-msp-card-photo" }) : /* @__PURE__ */ jsx(ProfileAvatar, { gender: profile.gender }) }),
8428
+ /* @__PURE__ */ jsx("div", { className: "ps-msp-card-circle", children: profile.photoBase64 ? /* @__PURE__ */ jsx("img", { src: profile.photoBase64, alt: profile.name }) : /* @__PURE__ */ jsx(UserIcon, { size: 32 }) }),
8361
8429
  /* @__PURE__ */ jsx("div", { className: "ps-msp-card-name", children: profile.name }),
8362
8430
  /* @__PURE__ */ jsxs("div", { className: "ps-msp-card-meta", children: [
8363
8431
  heightDisplay && /* @__PURE__ */ jsxs("div", { className: "ps-msp-meta-row", children: [
@@ -8427,6 +8495,7 @@ function MySizingProfilesView({
8427
8495
  onSelectProfile,
8428
8496
  onEditProfile,
8429
8497
  onSaveNewProfile,
8498
+ onSaveProfileMeasurements,
8430
8499
  onDeleteProfile,
8431
8500
  onClose,
8432
8501
  t
@@ -8458,9 +8527,7 @@ function MySizingProfilesView({
8458
8527
  onSelectProfile(viewingProfile.id);
8459
8528
  setViewingId(null);
8460
8529
  },
8461
- onEdit: () => {
8462
- onEditProfile(viewingProfile);
8463
- },
8530
+ onSaveMeasurements: (m) => onSaveProfileMeasurements(viewingProfile.id, m),
8464
8531
  onDelete: () => {
8465
8532
  onDeleteProfile(viewingProfile.id);
8466
8533
  setViewingId(null);
@@ -10908,7 +10975,34 @@ function PrimeStyleTryonInner({
10908
10975
  activeProfileId,
10909
10976
  onSelectProfile: (id) => {
10910
10977
  setActiveProfileId$1(id);
10911
- setView("body-profile");
10978
+ const p = profiles.find((x) => x.id === id);
10979
+ if (!p || !p.measurements || Object.keys(p.measurements).length === 0) {
10980
+ setView("body-profile");
10981
+ return;
10982
+ }
10983
+ setSizingResult(null);
10984
+ setSizingLoading(true);
10985
+ setEstimationDone(true);
10986
+ setView("size-result");
10987
+ recommendForProduct({
10988
+ productId: effectiveProductId,
10989
+ productTitle,
10990
+ productImage,
10991
+ sizeGuideData,
10992
+ profile: p,
10993
+ apiUrl,
10994
+ skipCache: true
10995
+ // always re-recommend on explicit profile select
10996
+ }).then((res) => {
10997
+ if (res?.raw) {
10998
+ setSizingResult(res.raw);
10999
+ }
11000
+ }).catch(() => {
11001
+ }).finally(() => setSizingLoading(false));
11002
+ },
11003
+ onSaveProfileMeasurements: (id, measurements) => {
11004
+ updateProfileMeasurements(id, measurements, profiles.find((x) => x.id === id)?.measurementsUnit || "cm");
11005
+ setProfiles(lsGet("profiles", []));
10912
11006
  },
10913
11007
  onEditProfile: (p) => {
10914
11008
  setProfileDetail(p);