@primestyleai/tryon 5.10.144 → 5.10.169

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.
@@ -10098,20 +10098,15 @@ function parseNum(s) {
10098
10098
  function computeFit(userValue, chartRange, unit) {
10099
10099
  const { min: rMin, max: rMax } = parseRange(chartRange);
10100
10100
  if (rMin === 0 && rMax === 0) return "good";
10101
- const range = rMax - rMin;
10102
- const threshold = range > 0 ? range * 0.5 : rMin * 0.05 || 3;
10103
- const perfectTol = unit === "cm" ? 2.54 : unit === "mm" ? 25.4 : 1;
10104
- if (userValue > rMin - perfectTol && userValue < rMax + perfectTol) return "good";
10105
- if (userValue < rMin) {
10106
- const diff2 = rMin - userValue;
10107
- if (diff2 > threshold * 2) return "too-loose";
10108
- if (diff2 > threshold) return "loose";
10109
- return "a-bit-loose";
10110
- }
10111
- const diff = userValue - rMax;
10112
- if (diff > threshold * 2) return "too-tight";
10113
- if (diff > threshold) return "tight";
10114
- return "a-bit-tight";
10101
+ const perfectTol = unit === "cm" ? 0.25 : unit === "mm" ? 2.5 : 0.1;
10102
+ const aBitTol = unit === "cm" ? 1.27 : unit === "mm" ? 12.7 : 0.5;
10103
+ const tooFarTol = unit === "cm" ? 5.08 : unit === "mm" ? 50.8 : 2;
10104
+ if (userValue >= rMin - perfectTol && userValue <= rMax + perfectTol) return "good";
10105
+ const isUnder = userValue < rMin;
10106
+ const diff = isUnder ? rMin - userValue : userValue - rMax;
10107
+ if (diff > tooFarTol) return isUnder ? "too-loose" : "too-tight";
10108
+ if (diff > aBitTol) return isUnder ? "loose" : "tight";
10109
+ return isUnder ? "a-bit-loose" : "a-bit-tight";
10115
10110
  }
10116
10111
  const AREA_TO_POSE_KEY = {
10117
10112
  chest: "chest",
@@ -10151,10 +10146,44 @@ function buildFitInfo(matchDetails, poseLines, unit) {
10151
10146
  function buildSilhouetteContext(sizingResult, sizeGuide, selectedSizeOverride, userHeight, userWeight) {
10152
10147
  if (!sizingResult && !sizeGuide && !userHeight && !userWeight) return void 0;
10153
10148
  const out = {};
10154
- if (selectedSizeOverride) out.recommendedSize = selectedSizeOverride;
10155
- else if (sizingResult?.recommendedSize) out.recommendedSize = sizingResult.recommendedSize;
10149
+ const baseSize = (selectedSizeOverride || sizingResult?.recommendedSize || "").toString().trim();
10156
10150
  if (userHeight) out.userHeight = userHeight;
10157
10151
  if (userWeight) out.userWeight = userWeight;
10152
+ let chartRowLength = null;
10153
+ let chartRowFit = null;
10154
+ if (baseSize && sizeGuide?.headers?.length && sizeGuide.rows?.length) {
10155
+ const matchRow = sizeGuide.rows.find((r2) => (r2[0] ?? "").toString().trim() === baseSize);
10156
+ if (matchRow) {
10157
+ const hdrs = sizeGuide.headers;
10158
+ const bits = [];
10159
+ for (let i = 1; i < hdrs.length; i++) {
10160
+ const v2 = matchRow[i];
10161
+ const vStr = v2 == null ? "" : String(v2).trim();
10162
+ const h = hdrs[i] || "";
10163
+ if (chartRowFit == null && /^(fit|silhouette|category|body[\s_]?type)$/i.test(h) && vStr) {
10164
+ chartRowFit = vStr;
10165
+ continue;
10166
+ }
10167
+ if (vStr) bits.push(`${h} ${vStr}`);
10168
+ if (chartRowLength == null && /^length\b|inseam/i.test(h) && vStr) {
10169
+ chartRowLength = vStr;
10170
+ }
10171
+ }
10172
+ if (bits.length) out.recommendedSizeMeasurements = bits.join(", ");
10173
+ }
10174
+ }
10175
+ if (baseSize) {
10176
+ const prefixed = chartRowFit ? `${chartRowFit} ${baseSize}` : baseSize;
10177
+ const length = sizingResult?.recommendedLength || chartRowLength;
10178
+ if (length) {
10179
+ const lenStr = String(length).trim();
10180
+ const unit = (sizingResult?.unit || "in").toString();
10181
+ const hasUnit = /[a-z]/i.test(lenStr);
10182
+ out.recommendedSize = `${prefixed} / Length ${lenStr}${hasUnit ? "" : ` ${unit}`}`;
10183
+ } else {
10184
+ out.recommendedSize = prefixed;
10185
+ }
10186
+ }
10158
10187
  const seen = /* @__PURE__ */ new Set();
10159
10188
  const userLines = [];
10160
10189
  const push = (md2) => {
@@ -10806,6 +10835,40 @@ const STYLES$1 = `
10806
10835
  }
10807
10836
  .ps-tryon-header-icon:hover { border-color: var(--ps-accent); color: var(--ps-accent); }
10808
10837
  .ps-tryon-header-icon svg { stroke: currentColor; fill: none; width: clamp(14px, 0.9vw, 16px); height: clamp(14px, 0.9vw, 16px); }
10838
+ /* Labeled variant — icon + text. Auto width, vw-scaled padding/gap so the
10839
+ glyph and the label baseline-align cleanly across breakpoints. */
10840
+ .ps-tryon-header-icon-labeled {
10841
+ width: auto !important;
10842
+ padding: 0 clamp(8px, 0.6vw, 12px);
10843
+ gap: clamp(5px, 0.42vw, 8px);
10844
+ font-size: clamp(11px, 0.73vw, 13px);
10845
+ font-family: inherit;
10846
+ font-weight: 500;
10847
+ line-height: 1;
10848
+ white-space: nowrap;
10849
+ }
10850
+ .ps-tryon-header-icon-label { line-height: 1; }
10851
+ /* "Powered by PrimeStyleAI" — left-aligned tag in the modal header,
10852
+ links out to primestyleai.com in a new tab. */
10853
+ .ps-tryon-powered-by {
10854
+ margin-right: auto;
10855
+ font-size: clamp(10px, 0.68vw, 12px);
10856
+ color: #9aa0a6;
10857
+ letter-spacing: 0.04em;
10858
+ font-weight: 400;
10859
+ line-height: 1.4;
10860
+ text-transform: uppercase;
10861
+ white-space: nowrap;
10862
+ text-decoration: none;
10863
+ transition: color 0.2s;
10864
+ }
10865
+ .ps-tryon-powered-by:hover { color: #5a6168; }
10866
+ .ps-tryon-powered-by:hover strong { color: var(--ps-accent); }
10867
+ .ps-tryon-powered-by strong { color: #5a6168; font-weight: 600; letter-spacing: 0.02em; text-transform: none; transition: color 0.2s; }
10868
+ /* Mobile hamburger button (desktop: hidden — inline buttons handle this) */
10869
+ .ps-tryon-header-mobile-menu-btn { display: none; }
10870
+ .ps-tryon-header-mobile-backdrop { display: none; }
10871
+ .ps-tryon-header-mobile-menu { display: none; }
10809
10872
  .ps-tryon-close {
10810
10873
  width: 2.2vw; height: 2.2vw; display: flex; align-items: center; justify-content: center;
10811
10874
  background: none; border: none; color: var(--ps-modal-close-color, #999);
@@ -13812,15 +13875,112 @@ const STYLES$1 = `
13812
13875
  flex-shrink: 0;
13813
13876
  background: var(--ps-modal-header-bg, rgba(255,255,255,0.95)) !important;
13814
13877
  }
13815
- /* Mobile header icons: minimal flat glyphs, vertical hairline separators
13816
- * between adjacent icons (skip the first one). 24 px tap target keeps
13817
- * accessibility while the icon glyph shrinks to 14 px. */
13878
+ /* Mobile header icons: minimal flat glyphs, slightly bigger touch
13879
+ * targets than before so the icons read clearly at arms length, and
13880
+ * with extra spacing between adjacent buttons (gap below). Labels are
13881
+ * desktop-only — hide the inline text on mobile so it stays icon-only. */
13818
13882
  .ps-tryon-header-icon {
13819
- width: 24px !important; height: 24px !important;
13883
+ width: 34px !important; height: 34px !important;
13820
13884
  border: none !important; background: transparent !important;
13821
13885
  border-radius: 0 !important; padding: 0 !important;
13886
+ gap: 0 !important;
13887
+ }
13888
+ .ps-tryon-header-icon svg { width: 20px !important; height: 20px !important; }
13889
+ .ps-tryon-header-icon-labeled { padding: 0 !important; font-size: 0 !important; }
13890
+ .ps-tryon-header-icon-label { display: none !important; }
13891
+ .ps-tryon-header-minimal { gap: 10px !important; }
13892
+ .ps-tryon-powered-by {
13893
+ font-size: 10px !important;
13894
+ letter-spacing: 0.05em !important;
13895
+ }
13896
+ /* Mobile-only header collapse: Profile + History + LangSwitcher move
13897
+ into the hamburger popover. The inline icons render NOTHING on
13898
+ mobile so the header reads "PoweredBy ... [☰] [×]". */
13899
+ .ps-tryon-header-icon-labeled,
13900
+ .ps-tryon-header-minimal > .ps-tryon-lang-trigger { display: none !important; }
13901
+ .ps-tryon-header-mobile-menu-btn {
13902
+ display: flex !important;
13903
+ width: 34px !important; height: 34px !important;
13904
+ border: none !important; background: transparent !important;
13905
+ align-items: center !important; justify-content: center !important;
13906
+ color: var(--ps-text-secondary) !important;
13907
+ padding: 0 !important; cursor: pointer !important;
13908
+ }
13909
+ .ps-tryon-header-mobile-menu-btn:hover { color: var(--ps-accent) !important; }
13910
+ .ps-tryon-header-mobile-backdrop {
13911
+ display: block !important;
13912
+ position: fixed !important; inset: 0 !important;
13913
+ z-index: 2147483646 !important;
13914
+ background: rgba(0,0,0,0.18) !important;
13915
+ }
13916
+ .ps-tryon-header-mobile-menu {
13917
+ display: flex !important;
13918
+ flex-direction: column;
13919
+ position: fixed !important;
13920
+ z-index: 2147483647 !important;
13921
+ min-width: 168px;
13922
+ background: #fff;
13923
+ border-radius: 12px;
13924
+ box-shadow: 0 10px 28px rgba(0,0,0,0.14), 0 1px 2px rgba(0,0,0,0.06);
13925
+ overflow: hidden;
13926
+ animation: ps-tryon-mobile-menu-in 0.18s ease-out both;
13927
+ padding: 4px 0;
13928
+ }
13929
+ @keyframes ps-tryon-mobile-menu-in {
13930
+ from { opacity: 0; transform: translateY(-6px) scale(0.97); }
13931
+ to { opacity: 1; transform: none; }
13932
+ }
13933
+ .ps-tryon-header-mobile-menu-item {
13934
+ display: flex !important; align-items: center; gap: 10px;
13935
+ padding: 11px 14px !important;
13936
+ border: none !important; background: transparent !important;
13937
+ cursor: pointer !important;
13938
+ font: inherit; font-size: 14px !important; font-weight: 500 !important;
13939
+ color: var(--ps-text-primary, #111) !important;
13940
+ text-align: left !important;
13941
+ transition: background 0.15s;
13942
+ }
13943
+ .ps-tryon-header-mobile-menu-item:hover { background: rgba(0,0,0,0.04) !important; }
13944
+ .ps-tryon-header-mobile-menu-item svg {
13945
+ width: 18px !important; height: 18px !important;
13946
+ stroke: currentColor !important; fill: none !important;
13947
+ }
13948
+ .ps-tryon-header-mobile-menu-lang {
13949
+ border-top: 1px solid rgba(0,0,0,0.06);
13950
+ }
13951
+ /* Inside the popover, force the language trigger to match the
13952
+ Profile/History rows: same padding, gap, font-size, icon size. */
13953
+ .ps-tryon-header-mobile-menu-lang .ps-tryon-lang-trigger {
13954
+ display: flex !important;
13955
+ align-items: center !important;
13956
+ gap: 10px !important;
13957
+ width: 100% !important;
13958
+ padding: 11px 14px !important;
13959
+ min-height: 0 !important;
13960
+ border: none !important;
13961
+ background: transparent !important;
13962
+ border-radius: 0 !important;
13963
+ font: inherit !important;
13964
+ font-size: 14px !important;
13965
+ font-weight: 500 !important;
13966
+ color: var(--ps-text-primary, #111) !important;
13967
+ text-align: left !important;
13968
+ cursor: pointer !important;
13969
+ transition: background 0.15s !important;
13970
+ }
13971
+ .ps-tryon-header-mobile-menu-lang .ps-tryon-lang-trigger:hover { background: rgba(0,0,0,0.04) !important; }
13972
+ .ps-tryon-header-mobile-menu-lang .ps-tryon-lang-trigger svg {
13973
+ width: 18px !important; height: 18px !important;
13974
+ flex-shrink: 0 !important;
13975
+ }
13976
+ .ps-tryon-header-mobile-menu-lang .ps-tryon-lang-current {
13977
+ font-size: 14px !important;
13978
+ font-weight: 500 !important;
13979
+ flex: 1;
13980
+ }
13981
+ .ps-tryon-header-mobile-menu-lang .ps-tryon-lang-arrow {
13982
+ margin-left: auto;
13822
13983
  }
13823
- .ps-tryon-header-icon svg { width: 14px !important; height: 14px !important; }
13824
13984
  .ps-tryon-close {
13825
13985
  width: 24px !important; height: 24px !important;
13826
13986
  border: none !important; background: transparent !important;
@@ -19619,6 +19779,12 @@ function garmentIconForSection(name) {
19619
19779
  if (n2.includes("vest") || n2.includes("waistcoat") || n2.includes("gilet")) return garmentVestImg;
19620
19780
  return null;
19621
19781
  }
19782
+ function formatPillLabel(s) {
19783
+ if (!s) return s;
19784
+ const head = s.split("/")[0]?.trim() || s;
19785
+ const tokens = head.split(/\s+/);
19786
+ return tokens[tokens.length - 1] || s;
19787
+ }
19622
19788
  const SKELETON_CONNECTIONS = [
19623
19789
  ["nose", "leftShoulder"],
19624
19790
  ["nose", "rightShoulder"],
@@ -19872,7 +20038,7 @@ function MeasurementOverlay({ lines, fitRows, show, imgWidth, imgHeight }) {
19872
20038
  const x2 = line.x2 * W2;
19873
20039
  const cy = line.y * H2;
19874
20040
  const width = x2 - x1;
19875
- const curveDepth = width * 0.06;
20041
+ const ellipseRy = width * 0.12;
19876
20042
  const midX = (x1 + x2) / 2;
19877
20043
  const fitRow = fitRows.find((r2) => {
19878
20044
  const a = r2.area.toLowerCase().trim();
@@ -19882,31 +20048,54 @@ function MeasurementOverlay({ lines, fitRows, show, imgWidth, imgHeight }) {
19882
20048
  if (key === "hips" && (a.includes("hip") || a === "hips")) return true;
19883
20049
  return false;
19884
20050
  });
19885
- const color = fitRow ? fitColor(fitRow.fit) : "#2154EF";
20051
+ if (!fitRow) return null;
20052
+ const color = "rgba(255,255,255,0.95)";
19886
20053
  const delay = i * 0.35;
19887
- const curvePath = `M ${x1} ${cy} Q ${midX} ${cy + curveDepth} ${x2} ${cy}`;
19888
- const lineLen = width * 1.05;
20054
+ const frontArc = `M ${x1} ${cy} Q ${midX} ${cy + ellipseRy} ${x2} ${cy}`;
20055
+ const backArc = `M ${x1} ${cy} Q ${midX} ${cy - ellipseRy} ${x2} ${cy}`;
19889
20056
  const labelText = fitRow ? fitRow.fit === "good" ? "✓ Fit" : fitRow.isLength ? fitRow.fit.includes("short") || fitRow.fit.includes("tight") ? "Short" : "Long" : fitRow.fit.includes("tight") ? "Tight" : "Loose" : label;
19890
- const labelFont = Math.max(14, Math.round(22 * scale));
20057
+ const labelFont = Math.max(26, Math.round(48 * scale));
19891
20058
  const labelWidthEst = labelText.length * labelFont * 0.62;
19892
- const rightSpace = W2 - x2 - 12 * scale;
19893
- const flipLeft = rightSpace < labelWidthEst;
19894
- const labelX = flipLeft ? x1 - 10 * scale : x2 + 10 * scale;
19895
- const labelAnchor = flipLeft ? "end" : "start";
20059
+ const boxPadX = Math.max(10, Math.round(14 * scale));
20060
+ const boxPadY = Math.max(6, Math.round(10 * scale));
20061
+ const boxW = labelWidthEst + boxPadX * 2;
20062
+ const boxH = labelFont + boxPadY * 2;
20063
+ const armLen = Math.max(28, Math.round(50 * scale));
20064
+ const rightSpace = W2 - x2 - boxW - armLen - 12 * scale;
20065
+ const flipLeft = rightSpace < 0;
19896
20066
  const tickX1 = flipLeft ? x1 : x2;
19897
- const tickX2 = flipLeft ? x1 - 6 * scale : x2 + 6 * scale;
20067
+ const tickX2 = flipLeft ? x1 - armLen : x2 + armLen;
20068
+ const boxX = flipLeft ? tickX2 - boxW : tickX2;
20069
+ const boxY = cy - boxH / 2;
20070
+ const boxFill = fitRow ? fitColor(fitRow.fit) : "rgba(255,255,255,0.95)";
20071
+ const arrowHeadW = Math.max(6, Math.round(10 * scale));
20072
+ const dashLen = Math.max(6, Math.round(8 * scale));
20073
+ const gapLen = Math.max(5, Math.round(7 * scale));
19898
20074
  return /* @__PURE__ */ jsxRuntimeExports.jsxs("g", { children: [
20075
+ /* @__PURE__ */ jsxRuntimeExports.jsx("g", { opacity: "0.45", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
20076
+ "path",
20077
+ {
20078
+ d: backArc,
20079
+ fill: "none",
20080
+ stroke: color,
20081
+ strokeWidth: strokeW,
20082
+ strokeLinecap: "round",
20083
+ strokeDasharray: `${dashLen} ${gapLen}`,
20084
+ opacity: "0",
20085
+ style: { animation: `ps-pose-fade 0.45s ease ${delay}s forwards` }
20086
+ }
20087
+ ) }),
19899
20088
  /* @__PURE__ */ jsxRuntimeExports.jsx(
19900
20089
  "path",
19901
20090
  {
19902
- d: curvePath,
20091
+ d: frontArc,
19903
20092
  fill: "none",
19904
20093
  stroke: color,
19905
20094
  strokeWidth: strokeW,
19906
20095
  strokeLinecap: "round",
19907
- strokeDasharray: lineLen,
19908
- strokeDashoffset: lineLen,
19909
- style: { animation: `ps-pose-draw 0.7s ease-out ${delay}s forwards` }
20096
+ strokeDasharray: `${dashLen} ${gapLen}`,
20097
+ opacity: "0",
20098
+ style: { animation: `ps-pose-fade 0.45s ease ${delay + 0.05}s forwards` }
19910
20099
  }
19911
20100
  ),
19912
20101
  /* @__PURE__ */ jsxRuntimeExports.jsx(
@@ -19917,7 +20106,7 @@ function MeasurementOverlay({ lines, fitRows, show, imgWidth, imgHeight }) {
19917
20106
  r: dotR,
19918
20107
  fill: color,
19919
20108
  opacity: "0",
19920
- style: { animation: `ps-pose-fade 0.3s ease ${delay + 0.5}s forwards` }
20109
+ style: { animation: `ps-pose-fade 0.3s ease ${delay + 0.4}s forwards` }
19921
20110
  }
19922
20111
  ),
19923
20112
  /* @__PURE__ */ jsxRuntimeExports.jsx(
@@ -19928,10 +20117,10 @@ function MeasurementOverlay({ lines, fitRows, show, imgWidth, imgHeight }) {
19928
20117
  r: dotR,
19929
20118
  fill: color,
19930
20119
  opacity: "0",
19931
- style: { animation: `ps-pose-fade 0.3s ease ${delay + 0.5}s forwards` }
20120
+ style: { animation: `ps-pose-fade 0.3s ease ${delay + 0.4}s forwards` }
19932
20121
  }
19933
20122
  ),
19934
- /* @__PURE__ */ jsxRuntimeExports.jsxs("g", { opacity: "0", style: { animation: `ps-pose-fade 0.4s ease ${delay + 0.6}s forwards` }, children: [
20123
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("g", { opacity: "0", style: { animation: `ps-pose-fade 0.4s ease ${delay + 0.5}s forwards` }, children: [
19935
20124
  /* @__PURE__ */ jsxRuntimeExports.jsx(
19936
20125
  "line",
19937
20126
  {
@@ -19940,20 +20129,45 @@ function MeasurementOverlay({ lines, fitRows, show, imgWidth, imgHeight }) {
19940
20129
  x2: tickX2,
19941
20130
  y2: cy,
19942
20131
  stroke: color,
19943
- strokeWidth: 2 * scale
20132
+ strokeWidth: Math.max(2, 2.5 * scale),
20133
+ strokeLinecap: "round"
20134
+ }
20135
+ ),
20136
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
20137
+ "polygon",
20138
+ {
20139
+ points: flipLeft ? `${tickX2 - arrowHeadW},${cy - arrowHeadW * 0.6} ${tickX2},${cy} ${tickX2 - arrowHeadW},${cy + arrowHeadW * 0.6}` : `${tickX2 + arrowHeadW},${cy - arrowHeadW * 0.6} ${tickX2},${cy} ${tickX2 + arrowHeadW},${cy + arrowHeadW * 0.6}`,
20140
+ fill: color
20141
+ }
20142
+ ),
20143
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
20144
+ "rect",
20145
+ {
20146
+ x: boxX,
20147
+ y: boxY,
20148
+ width: boxW,
20149
+ height: boxH,
20150
+ rx: boxH / 2,
20151
+ ry: boxH / 2,
20152
+ fill: boxFill,
20153
+ stroke: "rgba(0,0,0,0.18)",
20154
+ strokeWidth: 1
19944
20155
  }
19945
20156
  ),
19946
20157
  /* @__PURE__ */ jsxRuntimeExports.jsx(
19947
20158
  "text",
19948
20159
  {
19949
- x: labelX,
20160
+ x: boxX + boxW / 2,
19950
20161
  y: cy + 1 * scale,
19951
- fill: color,
20162
+ fill: "#FFFFFF",
19952
20163
  fontSize: labelFont,
19953
- fontWeight: "700",
20164
+ fontWeight: "800",
19954
20165
  fontFamily: "system-ui, -apple-system, sans-serif",
19955
20166
  dominantBaseline: "middle",
19956
- textAnchor: labelAnchor,
20167
+ textAnchor: "middle",
20168
+ stroke: "rgba(0,0,0,0.35)",
20169
+ strokeWidth: Math.max(2, scale * 2),
20170
+ paintOrder: "stroke",
19957
20171
  children: labelText
19958
20172
  }
19959
20173
  )
@@ -20091,7 +20305,22 @@ function SectionDetailView({
20091
20305
  const raw = section.rows.map((r2) => cellValFn(r2, sizeColIdx, sizeHeader)).filter(Boolean);
20092
20306
  return [...new Set(raw)];
20093
20307
  }, [section, sizeColIdx, sizeHeader]);
20094
- const displaySize = selectedSize || recSize;
20308
+ const displaySize = reactExports.useMemo(() => {
20309
+ const stripLen = (s) => {
20310
+ if (!s) return s;
20311
+ const head = (s.split("/")[0] ?? s).trim();
20312
+ const tokens = head.split(/\s+/);
20313
+ return tokens[tokens.length - 1] || s;
20314
+ };
20315
+ const sizeBase = selectedSize || recSize;
20316
+ if (!sizeBase || !selectedLength || !allSizes) return sizeBase;
20317
+ const baseBare = stripLen(sizeBase);
20318
+ const lengthLower = selectedLength.toLowerCase().trim();
20319
+ const match = Object.keys(allSizes).find(
20320
+ (k2) => stripLen(k2) === baseBare && k2.toLowerCase().includes(lengthLower)
20321
+ );
20322
+ return match || sizeBase;
20323
+ }, [selectedSize, recSize, selectedLength, allSizes]);
20095
20324
  const isRecommended = displaySize === recSize;
20096
20325
  reactExports.useMemo(() => {
20097
20326
  if (isRecommended) return null;
@@ -20100,7 +20329,21 @@ function SectionDetailView({
20100
20329
  const hasBadFit = details.some((d) => BAD_FIT.test(d.fit || ""));
20101
20330
  return hasBadFit ? t2("Not Recommended") : t2("Your Selection");
20102
20331
  }, [isRecommended, sectionResult, t2]);
20103
- const displaySizeLabel = selectedCountry && isRecommended && internationalSizes && internationalSizes[selectedCountry] ? internationalSizes[selectedCountry] : displaySize;
20332
+ const sizeFitPrefix = (() => {
20333
+ const target = displaySize?.toString().trim();
20334
+ if (!target || !section.headers?.length || !section.rows?.length) return "";
20335
+ const row = section.rows.find((r2) => (r2[0] ?? "").toString().trim() === target);
20336
+ if (!row) return "";
20337
+ for (let i = 1; i < section.headers.length; i++) {
20338
+ const h = section.headers[i] || "";
20339
+ const v2 = row[i];
20340
+ if (/^(fit|silhouette|category|body[\s_]?type)$/i.test(h) && v2 != null && String(v2).trim()) {
20341
+ return String(v2).trim();
20342
+ }
20343
+ }
20344
+ return "";
20345
+ })();
20346
+ const displaySizeLabel = selectedCountry && isRecommended && internationalSizes && internationalSizes[selectedCountry] ? internationalSizes[selectedCountry] : sizeFitPrefix ? `${sizeFitPrefix} ${displaySize}` : displaySize;
20104
20347
  const columnUnits = reactExports.useMemo(() => {
20105
20348
  const units = [];
20106
20349
  for (let i = 0; i < section.headers.length; i++) {
@@ -20186,16 +20429,17 @@ function SectionDetailView({
20186
20429
  const uNum = pNumFn(m2.userValue);
20187
20430
  if (uNum > 0) {
20188
20431
  const rMin = alt.min, rMax = alt.max;
20189
- const range = rMax - rMin;
20190
- const threshold = range > 0 ? range * 0.5 : rMin * 0.05 || 3;
20191
- const tol = Math.max((rMax || rMin) * 5e-3, 0.25);
20432
+ const isCm = /\bcm\b/i.test(rawChartRange);
20433
+ const aBitTol = isCm ? 1.27 : 0.5;
20434
+ const tooFarTol = isCm ? 5.08 : 2;
20435
+ const tol = Math.max((rMax || rMin) * 5e-3, isCm ? 0.25 : 0.1);
20192
20436
  if (uNum >= rMin - tol && uNum <= rMax + tol) fit = "good";
20193
20437
  else if (uNum < rMin) {
20194
20438
  const d = rMin - uNum;
20195
- fit = d > threshold * 2 ? "too-loose" : d > threshold ? "loose" : "a-bit-loose";
20439
+ fit = d > tooFarTol ? "too-loose" : d > aBitTol ? "loose" : "a-bit-loose";
20196
20440
  } else {
20197
20441
  const d = uNum - rMax;
20198
- fit = d > threshold * 2 ? "too-tight" : d > threshold ? "tight" : "a-bit-tight";
20442
+ fit = d > tooFarTol ? "too-tight" : d > aBitTol ? "tight" : "a-bit-tight";
20199
20443
  }
20200
20444
  }
20201
20445
  }
@@ -20291,20 +20535,85 @@ function SectionDetailView({
20291
20535
  (m2) => /inseam|length/i.test(m2.measurement) && !/neck|arm|sleeve|back|shoulder/i.test(m2.measurement)
20292
20536
  );
20293
20537
  const inseamFallback = lengthFromDetails ? (lengthFromDetails.chartRange || "").replace(/\s*(cm|in|inches)\s*/i, "").trim() : "";
20294
- const backendLength = secAny?.length || recLength || inseamFallback;
20538
+ const recLengthFromSuffix = (() => {
20539
+ const r2 = String(recSize || "");
20540
+ const slash = r2.indexOf("/");
20541
+ return slash > 0 ? r2.slice(slash + 1).trim() : "";
20542
+ })();
20543
+ const backendLength = secAny?.length || recLength || recLengthFromSuffix || inseamFallback;
20295
20544
  const backendAvailableSizes = secAny?.availableSizes || [];
20296
20545
  const backendAvailableLengths = secAny?.availableLengths || [];
20297
20546
  const finalDisplayLength = selectedLength || backendLength;
20298
20547
  const allSizesKeys = allSizes ? Object.keys(allSizes) : [];
20299
- const finalAllSizes = allSizesKeys.length > 0 ? allSizesKeys : backendAvailableSizes.length > 0 ? backendAvailableSizes : chartSizes;
20548
+ const stripLengthVariant = (s) => {
20549
+ if (!s) return s;
20550
+ const head = (s.split("/")[0] ?? s).trim();
20551
+ const tokens = head.split(/\s+/);
20552
+ return tokens[tokens.length - 1] || s;
20553
+ };
20554
+ const SIZE_LETTER_ORDER = {
20555
+ XXS: 0,
20556
+ XS: 1,
20557
+ S: 2,
20558
+ M: 3,
20559
+ L: 4,
20560
+ XL: 5,
20561
+ XXL: 6,
20562
+ "2XL": 6,
20563
+ XXXL: 7,
20564
+ "3XL": 7,
20565
+ "4XL": 8,
20566
+ "5XL": 9,
20567
+ "6XL": 10
20568
+ };
20569
+ const compareSizes = (a, b) => {
20570
+ const numA = parseFloat(a);
20571
+ const numB = parseFloat(b);
20572
+ if (!isNaN(numA) && !isNaN(numB)) {
20573
+ if (numA !== numB) return numA - numB;
20574
+ return a.localeCompare(b);
20575
+ }
20576
+ const oa2 = SIZE_LETTER_ORDER[a.toUpperCase()];
20577
+ const ob2 = SIZE_LETTER_ORDER[b.toUpperCase()];
20578
+ if (oa2 != null && ob2 != null) return oa2 - ob2;
20579
+ if (oa2 != null) return -1;
20580
+ if (ob2 != null) return 1;
20581
+ return a.localeCompare(b);
20582
+ };
20583
+ const dedupedSizes = (() => {
20584
+ const lengthLower = String(finalDisplayLength ?? "").toLowerCase().trim();
20585
+ const out = /* @__PURE__ */ new Map();
20586
+ for (const k2 of allSizesKeys) {
20587
+ const bare = stripLengthVariant(k2);
20588
+ if (!out.has(bare)) {
20589
+ out.set(bare, k2);
20590
+ } else if (lengthLower && k2.toLowerCase().includes(lengthLower)) {
20591
+ out.set(bare, k2);
20592
+ }
20593
+ }
20594
+ return Array.from(out.entries()).sort(([a], [b]) => compareSizes(a, b)).map(([, full]) => full);
20595
+ })();
20596
+ const finalAllSizes = dedupedSizes.length > 0 ? dedupedSizes : backendAvailableSizes.length > 0 ? backendAvailableSizes : chartSizes;
20300
20597
  const visibleSizes = (() => {
20301
20598
  if (finalAllSizes.length <= 3) return finalAllSizes;
20302
- const idx = finalAllSizes.indexOf(recSize);
20599
+ const recBare = stripLengthVariant(recSize || "");
20600
+ const idx = finalAllSizes.findIndex((k2) => stripLengthVariant(k2) === recBare);
20303
20601
  if (idx < 0) return finalAllSizes.slice(0, 3);
20304
20602
  const start = Math.max(0, Math.min(finalAllSizes.length - 3, idx - 1));
20305
20603
  return finalAllSizes.slice(start, start + 3);
20306
20604
  })();
20307
- const lengthOptions = backendAvailableLengths.length > 0 ? backendAvailableLengths : lengthSizes;
20605
+ const derivedLengths = (() => {
20606
+ const out = /* @__PURE__ */ new Set();
20607
+ for (const k2 of allSizesKeys) {
20608
+ const slash = k2.indexOf("/");
20609
+ if (slash > 0) {
20610
+ const tail = k2.slice(slash + 1).trim();
20611
+ if (tail) out.add(tail);
20612
+ }
20613
+ }
20614
+ return Array.from(out);
20615
+ })();
20616
+ const lengthOptions = backendAvailableLengths.length > 0 ? backendAvailableLengths : derivedLengths.length > 0 ? derivedLengths : lengthSizes;
20308
20617
  const visibleLengths = (() => {
20309
20618
  if (lengthOptions.length <= 3) return lengthOptions;
20310
20619
  const idx = lengthOptions.indexOf(backendLength);
@@ -20503,14 +20812,14 @@ function SectionDetailView({
20503
20812
  sectionFound !== false && visibleSizes.length > 1 && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-msd-sizes", children: [
20504
20813
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "ps-msd-sizes-label", children: t2("TRY ANOTHER SIZE") }),
20505
20814
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-msd-sizes-pills", children: visibleSizes.map((s) => {
20506
- const isActive = s === displaySize;
20815
+ const isActive = stripLengthVariant(s) === stripLengthVariant(displaySize || "");
20507
20816
  return /* @__PURE__ */ jsxRuntimeExports.jsx(
20508
20817
  "button",
20509
20818
  {
20510
20819
  type: "button",
20511
20820
  className: `ps-msd-size-pill${isActive ? " ps-active" : ""}`,
20512
- onClick: () => setSelectedSize(s === recSize ? null : s),
20513
- children: s
20821
+ onClick: () => setSelectedSize(stripLengthVariant(s) === stripLengthVariant(recSize || "") ? null : s),
20822
+ children: formatPillLabel(s)
20514
20823
  },
20515
20824
  s
20516
20825
  );
@@ -20743,11 +21052,11 @@ function SectionDetailView({
20743
21052
  t2("Size")
20744
21053
  ] }),
20745
21054
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { style: { display: "flex", gap: "0.3vw", flexWrap: "wrap" }, children: visibleSizes.map((s) => {
20746
- const isActive = s === displaySize;
21055
+ const isActive = stripLengthVariant(s) === stripLengthVariant(displaySize || "");
20747
21056
  return /* @__PURE__ */ jsxRuntimeExports.jsx(
20748
21057
  "button",
20749
21058
  {
20750
- onClick: () => setSelectedSize(s === recSize ? null : s),
21059
+ onClick: () => setSelectedSize(stripLengthVariant(s) === stripLengthVariant(recSize || "") ? null : s),
20751
21060
  style: {
20752
21061
  padding: "0.35vw 0.7vw",
20753
21062
  borderRadius: "0.35vw",
@@ -20762,7 +21071,7 @@ function SectionDetailView({
20762
21071
  minWidth: "2vw",
20763
21072
  textAlign: "center"
20764
21073
  },
20765
- children: s
21074
+ children: formatPillLabel(s)
20766
21075
  },
20767
21076
  s
20768
21077
  );
@@ -22920,10 +23229,20 @@ function CreateProfileWizard({ onSave, onCancel, apiUrl, apiKey, onPhotoPreview,
22920
23229
  };
22921
23230
  }, [imageStep, photoBase64]);
22922
23231
  reactExports.useEffect(() => {
22923
- if (imageStep !== "calculating" || !estimating) return;
22924
- const id2 = setInterval(() => setScanStageIdx((i) => i + 1), 1500);
22925
- return () => clearInterval(id2);
22926
- }, [imageStep, estimating]);
23232
+ if (imageStep !== "calculating") return;
23233
+ setScanStageIdx(0);
23234
+ }, [imageStep]);
23235
+ reactExports.useEffect(() => {
23236
+ if (imageStep !== "calculating") return;
23237
+ const STAGE_DWELL_MS = 1800;
23238
+ const TOTAL_STAGES = 5;
23239
+ if (scanStageIdx >= TOTAL_STAGES - 1) return;
23240
+ const id2 = setTimeout(
23241
+ () => setScanStageIdx((i) => Math.min(TOTAL_STAGES - 1, i + 1)),
23242
+ STAGE_DWELL_MS
23243
+ );
23244
+ return () => clearTimeout(id2);
23245
+ }, [imageStep, scanStageIdx]);
22927
23246
  const photoInputRef = reactExports.useRef(null);
22928
23247
  const nameInputRef = reactExports.useRef(null);
22929
23248
  const [nameShaking, setNameShaking] = reactExports.useState(false);
@@ -23190,6 +23509,7 @@ function CreateProfileWizard({ onSave, onCancel, apiUrl, apiKey, onPhotoPreview,
23190
23509
  setError(t2("Please enter a valid weight"));
23191
23510
  return;
23192
23511
  }
23512
+ const a = parseFloat(ageVal);
23193
23513
  if (isWomen && (!bandSize || !cupSize)) {
23194
23514
  setError(t2("Please select your bra band and cup size"));
23195
23515
  return;
@@ -23197,9 +23517,10 @@ function CreateProfileWizard({ onSave, onCancel, apiUrl, apiKey, onPhotoPreview,
23197
23517
  setError("");
23198
23518
  setImageStep("calculating");
23199
23519
  setEstimating(true);
23520
+ const MIN_HOLD_MS = 6e3;
23521
+ const minHold = new Promise((r2) => setTimeout(r2, MIN_HOLD_MS));
23200
23522
  if (onEstimate && photoBase64) {
23201
23523
  const heightRaw = unit === "in" ? (parseInt(heightFt, 10) || 0) * 12 + (parseInt(heightInch, 10) || 0) : parseFloat(heightVal);
23202
- const a = parseFloat(ageVal);
23203
23524
  try {
23204
23525
  const result = await onEstimate({
23205
23526
  photoBase64,
@@ -23216,7 +23537,9 @@ function CreateProfileWizard({ onSave, onCancel, apiUrl, apiKey, onPhotoPreview,
23216
23537
  } catch {
23217
23538
  }
23218
23539
  }
23540
+ await minHold;
23219
23541
  setEstimating(false);
23542
+ setImageStep("name-photo");
23220
23543
  return;
23221
23544
  }
23222
23545
  if (imageStep === "details") {
@@ -23460,7 +23783,8 @@ function CreateProfileWizard({ onSave, onCancel, apiUrl, apiKey, onPhotoPreview,
23460
23783
  setCupSize(null);
23461
23784
  setInlineBraOpen(null);
23462
23785
  setError("");
23463
- }
23786
+ },
23787
+ direction: "up"
23464
23788
  }
23465
23789
  ) })
23466
23790
  ] }),
@@ -23478,7 +23802,8 @@ function CreateProfileWizard({ onSave, onCancel, apiUrl, apiKey, onPhotoPreview,
23478
23802
  setBandSize(v2);
23479
23803
  setInlineBraOpen(null);
23480
23804
  setError("");
23481
- }
23805
+ },
23806
+ direction: "up"
23482
23807
  }
23483
23808
  ) })
23484
23809
  ] }),
@@ -23496,7 +23821,8 @@ function CreateProfileWizard({ onSave, onCancel, apiUrl, apiKey, onPhotoPreview,
23496
23821
  setCupSize(v2);
23497
23822
  setInlineBraOpen(null);
23498
23823
  setError("");
23499
- }
23824
+ },
23825
+ direction: "up"
23500
23826
  }
23501
23827
  ) })
23502
23828
  ] })
@@ -24025,7 +24351,8 @@ function CreateProfileWizard({ onSave, onCancel, apiUrl, apiKey, onPhotoPreview,
24025
24351
  setBandSize(null);
24026
24352
  setCupSize(null);
24027
24353
  setInlineBraOpen(null);
24028
- }
24354
+ },
24355
+ direction: "up"
24029
24356
  }
24030
24357
  ) })
24031
24358
  ] }),
@@ -24042,7 +24369,8 @@ function CreateProfileWizard({ onSave, onCancel, apiUrl, apiKey, onPhotoPreview,
24042
24369
  onSelect: (v2) => {
24043
24370
  setBandSize(v2);
24044
24371
  setInlineBraOpen(null);
24045
- }
24372
+ },
24373
+ direction: "up"
24046
24374
  }
24047
24375
  ) })
24048
24376
  ] }),
@@ -24189,16 +24517,6 @@ function CreateProfileWizard({ onSave, onCancel, apiUrl, apiKey, onPhotoPreview,
24189
24517
  } }),
24190
24518
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "ps-bp-inline-unit", children: unit === "in" ? "lbs" : "kg" })
24191
24519
  ] })
24192
- ] }),
24193
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-bp-inline-row", children: [
24194
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "ps-bp-inline-label", children: t2("AGE") }),
24195
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-bp-inline-input-group", children: [
24196
- /* @__PURE__ */ jsxRuntimeExports.jsx("input", { type: "number", inputMode: "numeric", className: "ps-bp-inline-input", value: ageVal, placeholder: t2("e.g. 30"), onChange: (e) => {
24197
- setAgeVal(e.target.value);
24198
- setError("");
24199
- } }),
24200
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "ps-bp-inline-unit", children: t2("years") })
24201
- ] })
24202
24520
  ] })
24203
24521
  ] }),
24204
24522
  error && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-cpw-error", children: error })
@@ -24206,12 +24524,12 @@ function CreateProfileWizard({ onSave, onCancel, apiUrl, apiKey, onPhotoPreview,
24206
24524
  mode === "image" && imageStep === "calculating" && (() => {
24207
24525
  const stages = [
24208
24526
  { title: t2("DETECTING POSE"), desc: t2("Identifying body landmarks from your photo.") },
24209
- { title: t2("SCANNING FRAME"), desc: t2("Mapping your proportions to calculate the perfect fit.") },
24527
+ { title: t2("SCANNING FRAME"), desc: t2("Our AI is mapping your proportions to calculate the perfect fit.") },
24210
24528
  { title: t2("ANALYZING BODY"), desc: t2("Measuring shoulders, chest, waist and hips.") },
24211
- { title: t2("COMPUTING DEPTH"), desc: t2("Using your height and weight to compute true circumferences.") },
24212
- { title: t2("FINALIZING"), desc: t2("Almost done — preparing your body profile.") }
24529
+ { title: t2("MATCHING SIZE"), desc: t2("Comparing your measurements to the size guide.") },
24530
+ { title: t2("FINALIZING RESULT"), desc: t2("Almost done — preparing your recommendation.") }
24213
24531
  ];
24214
- const stage = stages[scanStageIdx % stages.length] ?? stages[0];
24532
+ const stage = stages[Math.min(scanStageIdx, stages.length - 1)] ?? stages[0];
24215
24533
  const SKELETON_CONNECTIONS2 = [
24216
24534
  ["leftShoulder", "rightShoulder"],
24217
24535
  ["leftShoulder", "leftElbow"],
@@ -24342,11 +24660,13 @@ function CreateProfileWizard({ onSave, onCancel, apiUrl, apiKey, onPhotoPreview,
24342
24660
  mode === "image" && imageStep === "name-photo" && (() => {
24343
24661
  const heightOk = unit === "in" ? parseFloat(heightFt) > 0 || parseFloat(heightInch) > 0 : parseFloat(heightVal) > 0;
24344
24662
  const weightOk = parseFloat(weightVal) > 0;
24663
+ const braOk = !isWomen || !!bandSize && !!cupSize;
24345
24664
  const nameOk = !!name.trim();
24346
24665
  const photoOk = !!photoBase64;
24347
24666
  const analyzing = photoUploading;
24348
- const label = analyzing ? t2("Analyzing photo…") : !photoOk ? t2("Upload a photo") : !nameOk ? t2("Add a name for this profile") : !heightOk ? t2("Enter your height") : !weightOk ? t2("Enter your weight") : t2("Calculate My Body Parts");
24349
- const disabled = analyzing || !photoOk || !nameOk || !heightOk || !weightOk;
24667
+ const reviewMode = !!estimateResults;
24668
+ const label = analyzing ? t2("Analyzing photo…") : !photoOk ? t2("Upload a photo") : !nameOk ? t2("Add a name for this profile") : !heightOk ? t2("Enter your height") : !weightOk ? t2("Enter your weight") : !braOk ? t2("Select bra band and cup") : reviewMode ? t2("Save Profile") : t2("Calculate My Body Parts");
24669
+ const disabled = analyzing || !photoOk || !nameOk || !heightOk || !weightOk || !braOk;
24350
24670
  return /* @__PURE__ */ jsxRuntimeExports.jsx(
24351
24671
  "button",
24352
24672
  {
@@ -24358,6 +24678,15 @@ function CreateProfileWizard({ onSave, onCancel, apiUrl, apiKey, onPhotoPreview,
24358
24678
  nameInputRef.current?.focus();
24359
24679
  return;
24360
24680
  }
24681
+ if (reviewMode) {
24682
+ const payload = buildImagePayload();
24683
+ if (estimateResults) {
24684
+ payload.measurements = estimateResults;
24685
+ payload.measurementsUnit = "cm";
24686
+ }
24687
+ onSave(payload);
24688
+ return;
24689
+ }
24361
24690
  advanceImage();
24362
24691
  },
24363
24692
  disabled,
@@ -25619,15 +25948,31 @@ function BodyProfileView({
25619
25948
  const [hipProfile, setHipProfile] = reactExports.useState(null);
25620
25949
  const [seatProfile, setSeatProfile] = reactExports.useState(null);
25621
25950
  const [hoverDesc, setHoverDesc] = reactExports.useState("");
25622
- const [bandSize, setBandSize] = reactExports.useState(null);
25623
- const [cupSize, setCupSize] = reactExports.useState(null);
25951
+ const seedBand = activeProfile?.bandSize ?? null;
25952
+ const seedCup = activeProfile?.cupSize ?? null;
25953
+ const seedRegion = activeProfile?.braSizeRegion ?? activeProfile?.braRegion ?? null;
25954
+ const [bandSize, setBandSize] = reactExports.useState(seedBand);
25955
+ const [cupSize, setCupSize] = reactExports.useState(seedCup);
25624
25956
  const [braSizeRegion, setBraSizeRegion] = reactExports.useState(() => {
25957
+ if (seedRegion) return seedRegion;
25625
25958
  if (["US", "UK", "AU"].includes(sizingCountry)) return sizingCountry === "AU" ? "UK" : sizingCountry;
25626
25959
  if (["FR", "ES"].includes(sizingCountry)) return "FR";
25627
25960
  if (["IT"].includes(sizingCountry)) return "IT";
25628
25961
  if (["JP", "CN", "KR"].includes(sizingCountry)) return "JP";
25629
25962
  return "EU";
25630
25963
  });
25964
+ const seededBraProfileIdRef = reactExports.useRef(null);
25965
+ reactExports.useEffect(() => {
25966
+ const id2 = activeProfile?.id ?? null;
25967
+ if (!id2 || seededBraProfileIdRef.current === id2) return;
25968
+ seededBraProfileIdRef.current = id2;
25969
+ const b = activeProfile?.bandSize;
25970
+ const c = activeProfile?.cupSize;
25971
+ const r2 = activeProfile?.braSizeRegion ?? activeProfile?.braRegion;
25972
+ if (b) setBandSize(b);
25973
+ if (c) setCupSize(c);
25974
+ if (r2) setBraSizeRegion(r2);
25975
+ }, [activeProfile]);
25631
25976
  const [braRegionOpen, setBraRegionOpen] = reactExports.useState(false);
25632
25977
  const [inlineBraOpen, setInlineBraOpen] = reactExports.useState(null);
25633
25978
  const inlineBraWrapRef = reactExports.useRef(null);
@@ -26522,12 +26867,7 @@ function BodyProfileView({
26522
26867
  /* @__PURE__ */ jsxRuntimeExports.jsx("button", { className: `ps-bp-system-btn${isImperialMode ? " ps-bp-system-active" : ""}`, onClick: photoSwitchToImperial, type: "button", children: t2("Imperial") })
26523
26868
  ] }),
26524
26869
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { style: { marginTop: "auto", marginBottom: "auto" }, children: [
26525
- photoFromProfile && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { style: { display: "flex", justifyContent: "center", marginBottom: "0.7vw" }, children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-bp-loaded-pill", role: "status", children: [
26526
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "ps-bp-loaded-pill-dot", "aria-hidden": "true", children: "✓" }),
26527
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "ps-bp-loaded-pill-text", children: t2("Loaded from profile") }),
26528
- /* @__PURE__ */ jsxRuntimeExports.jsx("button", { type: "button", className: "ps-bp-loaded-pill-clear", onClick: handleClearFromProfile, children: t2("Clear") })
26529
- ] }) }),
26530
- hasActiveProfileWithMeasurements && activeProfileName && /* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "ps-bp-profile-hint", style: { textAlign: "center", margin: "0 0 0.5vw" }, children: [
26870
+ activeProfileName && /* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "ps-bp-profile-hint", style: { textAlign: "center", margin: "0 0 0.5vw" }, children: [
26531
26871
  t2("Using"),
26532
26872
  " ",
26533
26873
  /* @__PURE__ */ jsxRuntimeExports.jsx("strong", { children: activeProfileName }),
@@ -26825,7 +27165,7 @@ function BodyProfileView({
26825
27165
  onNext: hasActiveProfileWithMeasurements && onUseActiveProfile ? onUseActiveProfile : handleNext,
26826
27166
  canProceed: true,
26827
27167
  fastPathLabel: hasActiveProfileWithMeasurements ? t2("Find My Best Fit") + (getUnitLabel(hUnit) ? ` (${getUnitLabel(hUnit)})` : "") : void 0,
26828
- activeProfileName: hasActiveProfileWithMeasurements ? activeProfileName : null,
27168
+ activeProfileName: activeProfileName ?? null,
26829
27169
  onStartFresh,
26830
27170
  error,
26831
27171
  t: t2
@@ -26835,13 +27175,13 @@ function BodyProfileView({
26835
27175
  }
26836
27176
  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-bp-step ps-bp-step-enter", children: [
26837
27177
  /* @__PURE__ */ jsxRuntimeExports.jsx("h2", { className: "ps-bp-title", children: t2("Body Measurements") }),
26838
- hasActiveProfileWithMeasurements && activeProfileName && /* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "ps-bp-profile-hint", children: [
27178
+ activeProfileName && /* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "ps-bp-profile-hint", style: { textAlign: "center", margin: "0 0 0.5vw" }, children: [
26839
27179
  t2("Using"),
26840
27180
  " ",
26841
27181
  /* @__PURE__ */ jsxRuntimeExports.jsx("strong", { children: activeProfileName }),
26842
27182
  onStartFresh && /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
26843
27183
  " · ",
26844
- /* @__PURE__ */ jsxRuntimeExports.jsx("button", { type: "button", className: "ps-bp-profile-hint-link", onClick: onStartFresh, children: t2("start fresh") })
27184
+ /* @__PURE__ */ jsxRuntimeExports.jsx("button", { type: "button", className: "ps-bp-profile-hint-link", onClick: handleClearFromProfile, children: t2("start fresh") })
26845
27185
  ] })
26846
27186
  ] }),
26847
27187
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-bp-system-toggle", children: [
@@ -28172,6 +28512,10 @@ function PrimeStyleTryonInner({
28172
28512
  const [estimationLoading, setEstimationLoading] = reactExports.useState(false);
28173
28513
  const [profiles, setProfiles] = reactExports.useState(() => lsGet("profiles", []));
28174
28514
  const [history, setHistory] = reactExports.useState(() => lsGet("history", []));
28515
+ const [mobileMenuOpen, setMobileMenuOpen] = reactExports.useState(false);
28516
+ const mobileMenuBtnRef = reactExports.useRef(null);
28517
+ const [restoredProductImage, setRestoredProductImage] = reactExports.useState(null);
28518
+ const [restoredProductTitle, setRestoredProductTitle] = reactExports.useState(null);
28175
28519
  const [activeProfileId, setActiveProfileIdState] = reactExports.useState(() => getActiveProfileId());
28176
28520
  const [estimatingProfileIds, setEstimatingProfileIds] = reactExports.useState(() => /* @__PURE__ */ new Set());
28177
28521
  const [deleteConfirmId, setDeleteConfirmId] = reactExports.useState(null);
@@ -28313,18 +28657,25 @@ function PrimeStyleTryonInner({
28313
28657
  if (previewUrl) URL.revokeObjectURL(previewUrl);
28314
28658
  };
28315
28659
  }, [previewUrl]);
28660
+ const lockedScrollRef = reactExports.useRef(null);
28316
28661
  reactExports.useEffect(() => {
28317
- if (view !== "idle") {
28318
- const scrollY = window.scrollY;
28319
- const prevOverflow = document.body.style.overflow;
28320
- const prevOverscroll = document.body.style.overscrollBehavior;
28662
+ const isOpen = view !== "idle";
28663
+ if (isOpen && lockedScrollRef.current == null) {
28664
+ lockedScrollRef.current = {
28665
+ scrollY: window.scrollY,
28666
+ prevOverflow: document.body.style.overflow,
28667
+ prevOverscroll: document.body.style.overscrollBehavior
28668
+ };
28321
28669
  document.body.style.overflow = "hidden";
28322
28670
  document.body.style.overscrollBehavior = "none";
28323
- return () => {
28324
- document.body.style.overflow = prevOverflow;
28325
- document.body.style.overscrollBehavior = prevOverscroll;
28326
- window.scrollTo(0, scrollY);
28327
- };
28671
+ return;
28672
+ }
28673
+ if (!isOpen && lockedScrollRef.current != null) {
28674
+ const { scrollY, prevOverflow, prevOverscroll } = lockedScrollRef.current;
28675
+ lockedScrollRef.current = null;
28676
+ document.body.style.overflow = prevOverflow;
28677
+ document.body.style.overscrollBehavior = prevOverscroll;
28678
+ window.scrollTo(0, scrollY);
28328
28679
  }
28329
28680
  }, [view]);
28330
28681
  reactExports.useEffect(() => {
@@ -28524,16 +28875,15 @@ function PrimeStyleTryonInner({
28524
28875
  onOpen?.();
28525
28876
  }, [onOpen]);
28526
28877
  const handleClose = reactExports.useCallback(() => {
28878
+ const tryOnInFlight = tryOnProcessing;
28527
28879
  setView("idle");
28528
28880
  setSelectedFile(null);
28529
28881
  setDrawer(null);
28530
28882
  setProfileDetail(null);
28531
28883
  if (previewUrl) URL.revokeObjectURL(previewUrl);
28532
28884
  setPreviewUrl(null);
28533
- setResultImageUrl(null);
28534
28885
  setErrorMessage(null);
28535
28886
  setSizingMethod(null);
28536
- setSizingResult(null);
28537
28887
  setSizingLoading(false);
28538
28888
  setEstimatedValues(null);
28539
28889
  setEstimationLoading(false);
@@ -28541,15 +28891,19 @@ function PrimeStyleTryonInner({
28541
28891
  setProfileSaved(false);
28542
28892
  formRef.current = {};
28543
28893
  setFormGender("male");
28544
- historySavedRef.current = false;
28545
- unsubRef.current?.();
28546
- unsubRef.current = null;
28547
- if (pollingRef.current) {
28548
- clearInterval(pollingRef.current);
28549
- pollingRef.current = null;
28894
+ if (!tryOnInFlight) {
28895
+ setResultImageUrl(null);
28896
+ setSizingResult(null);
28897
+ historySavedRef.current = false;
28898
+ unsubRef.current?.();
28899
+ unsubRef.current = null;
28900
+ if (pollingRef.current) {
28901
+ clearInterval(pollingRef.current);
28902
+ pollingRef.current = null;
28903
+ }
28550
28904
  }
28551
28905
  onClose?.();
28552
- }, [onClose, previewUrl]);
28906
+ }, [onClose, previewUrl, tryOnProcessing]);
28553
28907
  const handleBack = reactExports.useCallback(() => {
28554
28908
  if (drawer) {
28555
28909
  setDrawer(null);
@@ -28937,6 +29291,8 @@ function PrimeStyleTryonInner({
28937
29291
  if (hipProfile) formRef.current.hipProfile = hipProfile;
28938
29292
  setSizingResult(null);
28939
29293
  setResultImageUrl(null);
29294
+ setRestoredProductImage(null);
29295
+ setRestoredProductTitle(null);
28940
29296
  if (previewUrl) URL.revokeObjectURL(previewUrl);
28941
29297
  setSelectedFile(null);
28942
29298
  selectedFileRef.current = null;
@@ -28969,6 +29325,14 @@ function PrimeStyleTryonInner({
28969
29325
  selectedFileRef.current = data.photoFile;
28970
29326
  const objUrl = data.photoFile ? URL.createObjectURL(data.photoFile) : data.photoBase64.startsWith("data:") ? data.photoBase64 : `data:image/jpeg;base64,${data.photoBase64}`;
28971
29327
  setPreviewUrl(objUrl);
29328
+ formRef.current.height = String(data.height);
29329
+ formRef.current.weight = String(data.weight);
29330
+ formRef.current.heightUnit = data.heightUnit;
29331
+ formRef.current.weightUnit = data.weightUnit;
29332
+ formRef.current.gender = data.gender;
29333
+ if (data.age != null) formRef.current.age = String(data.age);
29334
+ setRestoredProductImage(null);
29335
+ setRestoredProductTitle(null);
28972
29336
  completedRef.current = false;
28973
29337
  modelImageIdRef.current = null;
28974
29338
  noFitFoundRef.current = false;
@@ -29349,11 +29713,12 @@ function PrimeStyleTryonInner({
29349
29713
  if (noFitFoundRef.current) return;
29350
29714
  if (autoTryOnFiredRef.current) return;
29351
29715
  if (tryOnProcessing || resultImageUrl) return;
29716
+ if (!sizingResult) return;
29352
29717
  const file = selectedFile || selectedFileRef.current;
29353
29718
  if (!file) return;
29354
29719
  autoTryOnFiredRef.current = true;
29355
29720
  handleTryOnSubmit();
29356
- }, [view, selectedFile, tryOnProcessing, resultImageUrl, handleTryOnSubmit]);
29721
+ }, [view, selectedFile, sizingResult, tryOnProcessing, resultImageUrl, handleTryOnSubmit]);
29357
29722
  const handleDownload = reactExports.useCallback(() => {
29358
29723
  if (!resultImageUrl) return;
29359
29724
  if (resultImageUrl.startsWith("data:")) {
@@ -29493,9 +29858,7 @@ function PrimeStyleTryonInner({
29493
29858
  const file = selectedFile || selectedFileRef.current;
29494
29859
  if (file) {
29495
29860
  try {
29496
- const dataUrl = await compressImage(file, { maxDimension: 768, quality: 0.7 });
29497
- const blob = await (await fetch(dataUrl)).blob();
29498
- hasPhoto = await savePhoto(id2, blob);
29861
+ hasPhoto = await savePhoto(id2, file);
29499
29862
  } catch {
29500
29863
  }
29501
29864
  }
@@ -29550,7 +29913,7 @@ function PrimeStyleTryonInner({
29550
29913
  historyTryonSavedRef.current = false;
29551
29914
  saveHistoryEntry().catch(() => {
29552
29915
  });
29553
- } else if (view === "size-result" && sizingResult && resultImageUrl && !historyTryonSavedRef.current) {
29916
+ } else if ((view === "size-result" || view === "idle") && sizingResult && resultImageUrl && !historyTryonSavedRef.current) {
29554
29917
  historyTryonSavedRef.current = true;
29555
29918
  const overrides = pendingCustomSizesRef.current;
29556
29919
  const recommended = sizingResult?.recommendedSize ?? "";
@@ -29583,6 +29946,14 @@ function PrimeStyleTryonInner({
29583
29946
  await saveResult(id2, blob);
29584
29947
  } catch {
29585
29948
  }
29949
+ if (view === "idle") {
29950
+ unsubRef.current?.();
29951
+ unsubRef.current = null;
29952
+ if (pollingRef.current) {
29953
+ clearInterval(pollingRef.current);
29954
+ pollingRef.current = null;
29955
+ }
29956
+ }
29586
29957
  })();
29587
29958
  }
29588
29959
  } else if (view === "result" && resultImageUrl && !historySavedRef.current) {
@@ -29598,6 +29969,8 @@ function PrimeStyleTryonInner({
29598
29969
  historySavedRef.current = true;
29599
29970
  historyTryonSavedRef.current = !!entry.resultImageUrl;
29600
29971
  autoTryOnFiredRef.current = true;
29972
+ setRestoredProductImage(entry.productImage || null);
29973
+ setRestoredProductTitle(entry.productTitle || null);
29601
29974
  if (entry.sizingResult) {
29602
29975
  setSizingResult(entry.sizingResult);
29603
29976
  } else if (entry.recommendedSize) {
@@ -29645,7 +30018,12 @@ function PrimeStyleTryonInner({
29645
30018
  setSizingLoading(false);
29646
30019
  setTryOnProcessing(false);
29647
30020
  setTryOnStartedAt(null);
29648
- setActiveSection(null);
30021
+ const sectionNames = entry.sizeGuide?.sections ? Object.keys(entry.sizeGuide.sections) : [];
30022
+ if (sectionNames.length === 1) {
30023
+ setActiveSection(sectionNames[0]);
30024
+ } else {
30025
+ setActiveSection(null);
30026
+ }
29649
30027
  setDrawer(null);
29650
30028
  setView("size-result");
29651
30029
  }, []);
@@ -29821,9 +30199,9 @@ function PrimeStyleTryonInner({
29821
30199
  sizingResult,
29822
30200
  sizeGuide,
29823
30201
  resultImageUrl,
29824
- productImage,
29825
- productImages,
29826
- productTitle,
30202
+ productImage: restoredProductImage || productImage,
30203
+ productImages: restoredProductImage ? [restoredProductImage] : productImages,
30204
+ productTitle: restoredProductTitle || productTitle,
29827
30205
  productMaterial,
29828
30206
  productDescription,
29829
30207
  sizingUnit,
@@ -30102,8 +30480,21 @@ function PrimeStyleTryonInner({
30102
30480
  view !== "idle" && typeof document !== "undefined" && reactDomExports.createPortal(
30103
30481
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: cx("ps-tryon-overlay", cn.overlay), style: cssVars, "data-ps-tryon-portal": true, children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: cx(`ps-tryon-modal${view === "result" && resultImageUrl && sizingResult || view === "size-result" || view === "estimation-review" || view === "body-profile" || view === "profiles" || view === "no-chart" || view === "photo-guide" ? " ps-tryon-modal-wide" : ""}`, cn.modal), onClick: (e) => e.stopPropagation(), children: [
30104
30482
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: cx("ps-tryon-header ps-tryon-header-minimal", cn.header), children: [
30483
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
30484
+ "a",
30485
+ {
30486
+ className: "ps-tryon-powered-by",
30487
+ href: "https://primestyleai.com",
30488
+ target: "_blank",
30489
+ rel: "noopener noreferrer",
30490
+ children: [
30491
+ "Powered by ",
30492
+ /* @__PURE__ */ jsxRuntimeExports.jsx("strong", { children: "PrimeStyleAI" })
30493
+ ]
30494
+ }
30495
+ ),
30105
30496
  /* @__PURE__ */ jsxRuntimeExports.jsx(LangSwitcher, { activeLocale, onSelect: setActiveLocale }),
30106
- /* @__PURE__ */ jsxRuntimeExports.jsx("button", { className: "ps-tryon-header-icon", title: t2("Profiles"), onClick: () => {
30497
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("button", { type: "button", className: "ps-tryon-header-icon ps-tryon-header-icon-labeled", title: t2("Profiles"), onClick: () => {
30107
30498
  if (drawer) setDrawer(null);
30108
30499
  if (view === "profiles") {
30109
30500
  setView(prevViewRef.current || "body-profile");
@@ -30112,8 +30503,11 @@ function PrimeStyleTryonInner({
30112
30503
  prevViewRef.current = view;
30113
30504
  setView("profiles");
30114
30505
  }
30115
- }, children: /* @__PURE__ */ jsxRuntimeExports.jsx(UserIcon, {}) }),
30116
- /* @__PURE__ */ jsxRuntimeExports.jsx("button", { className: "ps-tryon-header-icon", title: t2("History"), onClick: () => {
30506
+ }, children: [
30507
+ /* @__PURE__ */ jsxRuntimeExports.jsx(UserIcon, {}),
30508
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "ps-tryon-header-icon-label", children: t2("Profile") })
30509
+ ] }),
30510
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("button", { type: "button", className: "ps-tryon-header-icon ps-tryon-header-icon-labeled", title: t2("History"), onClick: () => {
30117
30511
  if (view === "profiles") {
30118
30512
  setView(prevViewRef.current || "body-profile");
30119
30513
  prevViewRef.current = null;
@@ -30124,9 +30518,109 @@ function PrimeStyleTryonInner({
30124
30518
  prevViewRef.current = prevViewRef.current || view;
30125
30519
  setDrawer("history");
30126
30520
  }
30127
- }, children: /* @__PURE__ */ jsxRuntimeExports.jsx(ClockIcon, {}) }),
30128
- /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: handleClose, className: cx("ps-tryon-close", cn.closeButton), children: /* @__PURE__ */ jsxRuntimeExports.jsx(XIcon, {}) })
30521
+ }, children: [
30522
+ /* @__PURE__ */ jsxRuntimeExports.jsx(ClockIcon, {}),
30523
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "ps-tryon-header-icon-label", children: t2("History") })
30524
+ ] }),
30525
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
30526
+ "button",
30527
+ {
30528
+ type: "button",
30529
+ ref: mobileMenuBtnRef,
30530
+ className: "ps-tryon-header-mobile-menu-btn",
30531
+ "aria-label": t2("Menu"),
30532
+ "aria-expanded": mobileMenuOpen,
30533
+ onClick: () => setMobileMenuOpen((v2) => !v2),
30534
+ children: /* @__PURE__ */ jsxRuntimeExports.jsxs("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", width: "20", height: "20", children: [
30535
+ /* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: "3", y1: "6", x2: "21", y2: "6" }),
30536
+ /* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: "3", y1: "12", x2: "21", y2: "12" }),
30537
+ /* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: "3", y1: "18", x2: "21", y2: "18" })
30538
+ ] })
30539
+ }
30540
+ ),
30541
+ /* @__PURE__ */ jsxRuntimeExports.jsx("button", { type: "button", onClick: handleClose, className: cx("ps-tryon-close", cn.closeButton), children: /* @__PURE__ */ jsxRuntimeExports.jsx(XIcon, {}) })
30129
30542
  ] }),
30543
+ mobileMenuOpen && typeof document !== "undefined" && reactDomExports.createPortal(
30544
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
30545
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
30546
+ "div",
30547
+ {
30548
+ className: "ps-tryon-header-mobile-backdrop",
30549
+ onClick: () => setMobileMenuOpen(false)
30550
+ }
30551
+ ),
30552
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
30553
+ "div",
30554
+ {
30555
+ className: "ps-tryon-header-mobile-menu",
30556
+ role: "menu",
30557
+ style: (() => {
30558
+ const r2 = mobileMenuBtnRef.current?.getBoundingClientRect();
30559
+ if (!r2) return void 0;
30560
+ return {
30561
+ top: `${Math.round(r2.bottom + 6)}px`,
30562
+ right: `${Math.round(window.innerWidth - r2.right)}px`
30563
+ };
30564
+ })(),
30565
+ children: [
30566
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
30567
+ "button",
30568
+ {
30569
+ type: "button",
30570
+ className: "ps-tryon-header-mobile-menu-item",
30571
+ role: "menuitem",
30572
+ onClick: () => {
30573
+ setMobileMenuOpen(false);
30574
+ if (drawer) setDrawer(null);
30575
+ if (view === "profiles") {
30576
+ setView(prevViewRef.current || "body-profile");
30577
+ prevViewRef.current = null;
30578
+ } else {
30579
+ prevViewRef.current = view;
30580
+ setView("profiles");
30581
+ }
30582
+ },
30583
+ children: [
30584
+ /* @__PURE__ */ jsxRuntimeExports.jsx(UserIcon, {}),
30585
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: t2("Profile") })
30586
+ ]
30587
+ }
30588
+ ),
30589
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
30590
+ "button",
30591
+ {
30592
+ type: "button",
30593
+ className: "ps-tryon-header-mobile-menu-item",
30594
+ role: "menuitem",
30595
+ onClick: () => {
30596
+ setMobileMenuOpen(false);
30597
+ if (view === "profiles") {
30598
+ setView(prevViewRef.current || "body-profile");
30599
+ prevViewRef.current = null;
30600
+ }
30601
+ if (drawer === "history") {
30602
+ setDrawer(null);
30603
+ } else {
30604
+ prevViewRef.current = prevViewRef.current || view;
30605
+ setDrawer("history");
30606
+ }
30607
+ },
30608
+ children: [
30609
+ /* @__PURE__ */ jsxRuntimeExports.jsx(ClockIcon, {}),
30610
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: t2("History") })
30611
+ ]
30612
+ }
30613
+ ),
30614
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-tryon-header-mobile-menu-lang", children: /* @__PURE__ */ jsxRuntimeExports.jsx(LangSwitcher, { activeLocale, onSelect: (l2) => {
30615
+ setActiveLocale(l2);
30616
+ setMobileMenuOpen(false);
30617
+ } }) })
30618
+ ]
30619
+ }
30620
+ )
30621
+ ] }),
30622
+ document.body
30623
+ ),
30130
30624
  view !== "body-profile" && view !== "processing" && !(view === "size-result" && sizeGuide?.sections && Object.keys(sizeGuide.sections).length > 1) && /* @__PURE__ */ jsxRuntimeExports.jsx(Stepper, { view, stepIndex }),
30131
30625
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { ref: bodyRef, className: cx("ps-tryon-body", cn.body), style: { position: "relative", overflow: drawer ? "hidden" : void 0 }, children: [
30132
30626
  showBackButton && /* @__PURE__ */ jsxRuntimeExports.jsxs("button", { className: "ps-tryon-back-btn", onClick: handleBack, children: [