@primestyleai/tryon 5.10.144 → 5.10.170

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