@primestyleai/tryon 5.8.39 → 5.8.40

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.
@@ -2,7 +2,7 @@ import type { TranslateFn } from "../../i18n";
2
2
  import type { FitAreaInfo } from "../../types";
3
3
  import type { ViewState, SizeGuide, SizingResult } from "../types";
4
4
  import type { BodyLandmarks } from "../../pose-detect";
5
- export declare function SizeResultView({ sizingLoading, sizingResult, sizeGuide, resultImageUrl, productImage, productTitle, sizingUnit, setView, handleDownload, onRetryWithFit, retryLoading, selectedFile, previewUrl, handleFileSelect, handleRemovePreview, handleTryOnSubmit, tryOnProcessing, bodyLandmarks, estimationDone, activeSection, setActiveSection, onResetTryOn, onClose, userHeightCm, t, }: {
5
+ export declare function SizeResultView({ sizingLoading, sizingResult, sizeGuide, resultImageUrl, productImage, productTitle, sizingUnit, setView, handleDownload, onRetryWithFit, retryLoading, selectedFile, previewUrl, handleFileSelect, handleRemovePreview, handleTryOnSubmit, tryOnProcessing, bodyLandmarks, faceLandmarks, measurementType, estimationDone, activeSection, setActiveSection, onResetTryOn, onClose, userHeightCm, t, }: {
6
6
  estimationDone?: boolean;
7
7
  sizingLoading: boolean;
8
8
  sizingResult: SizingResult | null;
@@ -22,6 +22,8 @@ export declare function SizeResultView({ sizingLoading, sizingResult, sizeGuide,
22
22
  handleTryOnSubmit: (overrideFile?: File) => void;
23
23
  tryOnProcessing?: boolean;
24
24
  bodyLandmarks?: BodyLandmarks | null;
25
+ faceLandmarks?: import("../../face-detect").FaceLandmarks | null;
26
+ measurementType?: "body" | "face" | "head" | "foot";
25
27
  activeSection: string | null;
26
28
  setActiveSection: (s: string | null) => void;
27
29
  /** Clear try-on result + uploaded photo so the user can retry from the photo guide. */
@@ -13092,6 +13092,101 @@ const STYLES$1 = `
13092
13092
  }
13093
13093
  .ps-pm-preview-remove:hover { background: #FFFFFF; }
13094
13094
 
13095
+ /* ── Mobile age-gate overlay ── */
13096
+ .ps-pm-preview-blurred {
13097
+ filter: blur(6px) saturate(0.7);
13098
+ pointer-events: none;
13099
+ user-select: none;
13100
+ }
13101
+ .ps-pm-age-gate {
13102
+ position: absolute; inset: 0;
13103
+ display: flex; align-items: center; justify-content: center;
13104
+ padding: max(16px, 4vw);
13105
+ background: rgba(255, 255, 255, 0.55);
13106
+ backdrop-filter: blur(8px);
13107
+ -webkit-backdrop-filter: blur(8px);
13108
+ z-index: 2;
13109
+ animation: ps-pm-age-gate-in 0.28s ease-out both;
13110
+ }
13111
+ @keyframes ps-pm-age-gate-in {
13112
+ 0% { opacity: 0; transform: scale(0.97); }
13113
+ 100% { opacity: 1; transform: scale(1); }
13114
+ }
13115
+ .ps-pm-age-gate-card {
13116
+ width: 100%; max-width: max(280px, 82vw);
13117
+ padding: max(18px, 4.6vw) max(16px, 4.2vw);
13118
+ background: #FFFFFF;
13119
+ border: 1px solid var(--ps-border-subtle);
13120
+ border-radius: max(12px, 3vw);
13121
+ box-shadow: 0 20px 40px -12px rgba(17, 24, 39, 0.25),
13122
+ 0 8px 16px -8px rgba(17, 24, 39, 0.15);
13123
+ display: flex; flex-direction: column;
13124
+ align-items: center; text-align: center;
13125
+ gap: max(8px, 2vw);
13126
+ }
13127
+ .ps-pm-age-gate-eyebrow {
13128
+ font-size: max(10px, 2.6vw); font-weight: 700;
13129
+ letter-spacing: 0.14em; text-transform: uppercase;
13130
+ color: var(--ps-accent);
13131
+ }
13132
+ .ps-pm-age-gate-eyebrow-blocked { color: #C02626; }
13133
+ .ps-pm-age-gate-question {
13134
+ font-size: max(14px, 3.8vw); font-weight: 600;
13135
+ line-height: 1.35; color: var(--ps-text-primary); margin: 0;
13136
+ }
13137
+ .ps-pm-age-gate-actions {
13138
+ display: flex; flex-direction: column; gap: max(8px, 2vw);
13139
+ width: 100%; margin-top: max(4px, 1vw);
13140
+ }
13141
+ .ps-pm-age-gate-btn {
13142
+ width: 100%;
13143
+ padding: max(11px, 2.9vw) max(14px, 3.6vw);
13144
+ border-radius: 999px;
13145
+ font-family: inherit;
13146
+ font-size: max(12px, 3.2vw); font-weight: 700;
13147
+ letter-spacing: 0.02em;
13148
+ cursor: pointer;
13149
+ transition: background 0.18s, border-color 0.18s, color 0.18s;
13150
+ }
13151
+ .ps-pm-age-gate-btn-primary {
13152
+ background: var(--ps-accent);
13153
+ color: #FFFFFF;
13154
+ border: 1.5px solid var(--ps-accent);
13155
+ }
13156
+ .ps-pm-age-gate-btn-secondary {
13157
+ background: transparent;
13158
+ color: var(--ps-text-primary);
13159
+ border: 1.5px solid var(--ps-border-color);
13160
+ }
13161
+ .ps-pm-age-gate-btn-secondary:active {
13162
+ background: var(--ps-bg-secondary);
13163
+ }
13164
+
13165
+ /* ── Mobile legal notice ── */
13166
+ .ps-pm-legal-notice {
13167
+ margin-top: max(10px, 2.6vw);
13168
+ background: rgba(33, 84, 239, 0.04);
13169
+ border: 1px solid rgba(33, 84, 239, 0.16);
13170
+ border-radius: max(10px, 2.6vw);
13171
+ padding: max(10px, 2.6vw) max(12px, 3.1vw);
13172
+ display: flex; flex-direction: column;
13173
+ gap: max(4px, 1vw);
13174
+ }
13175
+ .ps-pm-legal-notice-head {
13176
+ display: flex; align-items: center;
13177
+ gap: max(6px, 1.5vw);
13178
+ font-size: max(10px, 2.6vw); font-weight: 700;
13179
+ letter-spacing: 0.12em; text-transform: uppercase;
13180
+ color: var(--ps-accent);
13181
+ }
13182
+ .ps-pm-legal-notice-head svg { width: max(13px, 3.4vw); height: max(13px, 3.4vw); }
13183
+ .ps-pm-legal-notice-body {
13184
+ margin: 0;
13185
+ font-size: max(11px, 2.9vw);
13186
+ line-height: 1.5;
13187
+ color: var(--ps-text-secondary);
13188
+ }
13189
+
13095
13190
  /* Checklist for accuracy card */
13096
13191
  .ps-pm-checklist {
13097
13192
  display: flex; gap: max(12px, 3.1vw);
@@ -14953,6 +15048,127 @@ const STYLES$1 = `
14953
15048
  transition: background 0.18s;
14954
15049
  }
14955
15050
  .ps-cpw-photo-retake:hover { background: rgba(33, 84, 239, 0.08); }
15051
+
15052
+ /* ── Age-gate overlay on the dropzone ──
15053
+ Dropzone stays visible but blurred; overlay shows a premium card
15054
+ with the 18+ confirmation question and two pill buttons. */
15055
+ .ps-cpw-dropzone-wrap {
15056
+ position: relative;
15057
+ flex: 1; min-height: 0;
15058
+ width: 100%; box-sizing: border-box;
15059
+ display: flex; flex-direction: column;
15060
+ }
15061
+ .ps-cpw-dropzone-blurred {
15062
+ filter: blur(6px) saturate(0.7);
15063
+ pointer-events: none;
15064
+ user-select: none;
15065
+ }
15066
+ .ps-cpw-age-gate {
15067
+ position: absolute; inset: 0;
15068
+ display: flex; align-items: center; justify-content: center;
15069
+ padding: clamp(12px, 1vw, 24px);
15070
+ border-radius: clamp(10px, 0.75vw, 16px);
15071
+ background: rgba(255, 255, 255, 0.55);
15072
+ backdrop-filter: blur(8px);
15073
+ -webkit-backdrop-filter: blur(8px);
15074
+ z-index: 2;
15075
+ animation: ps-cpw-age-gate-in 0.28s ease-out both;
15076
+ }
15077
+ @keyframes ps-cpw-age-gate-in {
15078
+ 0% { opacity: 0; transform: scale(0.97); }
15079
+ 100% { opacity: 1; transform: scale(1); }
15080
+ }
15081
+ .ps-cpw-age-gate-card {
15082
+ width: 100%; max-width: clamp(240px, 24vw, 420px);
15083
+ padding: clamp(16px, 1.4vw, 28px) clamp(18px, 1.6vw, 32px);
15084
+ background: #FFFFFF;
15085
+ border: 1px solid var(--ps-border-subtle);
15086
+ border-radius: clamp(10px, 0.9vw, 18px);
15087
+ box-shadow: 0 20px 40px -12px rgba(17, 24, 39, 0.25),
15088
+ 0 8px 16px -8px rgba(17, 24, 39, 0.15);
15089
+ display: flex; flex-direction: column;
15090
+ align-items: center; text-align: center;
15091
+ gap: clamp(8px, 0.7vw, 14px);
15092
+ }
15093
+ .ps-cpw-age-gate-eyebrow {
15094
+ font-size: clamp(9px, 0.62vw, 11px);
15095
+ font-weight: 700;
15096
+ letter-spacing: 0.18em;
15097
+ text-transform: uppercase;
15098
+ color: var(--ps-accent);
15099
+ }
15100
+ .ps-cpw-age-gate-eyebrow-blocked { color: #C02626; }
15101
+ .ps-cpw-age-gate-question {
15102
+ font-size: clamp(13px, 0.95vw, 18px);
15103
+ font-weight: 600;
15104
+ line-height: 1.35;
15105
+ color: var(--ps-text-primary);
15106
+ margin: 0;
15107
+ }
15108
+ .ps-cpw-age-gate-actions {
15109
+ display: flex; gap: clamp(8px, 0.65vw, 14px);
15110
+ width: 100%;
15111
+ margin-top: clamp(4px, 0.35vw, 8px);
15112
+ }
15113
+ .ps-cpw-age-gate-btn {
15114
+ flex: 1;
15115
+ padding: clamp(9px, 0.75vw, 14px) clamp(12px, 1vw, 18px);
15116
+ border-radius: 999px;
15117
+ font-family: inherit;
15118
+ font-size: clamp(11px, 0.78vw, 14px);
15119
+ font-weight: 700;
15120
+ letter-spacing: 0.02em;
15121
+ cursor: pointer;
15122
+ transition: transform 0.18s, background 0.18s, border-color 0.18s, color 0.18s;
15123
+ }
15124
+ .ps-cpw-age-gate-btn:hover { transform: translateY(-1px); }
15125
+ .ps-cpw-age-gate-btn-primary {
15126
+ background: var(--ps-accent);
15127
+ color: #FFFFFF;
15128
+ border: 1.5px solid var(--ps-accent);
15129
+ }
15130
+ .ps-cpw-age-gate-btn-primary:hover { background: color-mix(in srgb, var(--ps-accent) 88%, #000); }
15131
+ .ps-cpw-age-gate-btn-secondary {
15132
+ background: transparent;
15133
+ color: var(--ps-text-primary);
15134
+ border: 1.5px solid var(--ps-border-color);
15135
+ }
15136
+ .ps-cpw-age-gate-btn-secondary:hover {
15137
+ background: var(--ps-bg-secondary);
15138
+ border-color: var(--ps-text-muted);
15139
+ }
15140
+ .ps-cpw-age-gate-card-blocked { border-color: rgba(192, 38, 38, 0.35); }
15141
+
15142
+ /* ── Legal notice card on the right column ──
15143
+ Soft neutral card with a small shield icon; matches photo-guide width. */
15144
+ .ps-cpw-legal-notice {
15145
+ background: rgba(33, 84, 239, 0.04);
15146
+ border: 1px solid rgba(33, 84, 239, 0.16);
15147
+ border-radius: clamp(10px, 0.75vw, 16px);
15148
+ padding: clamp(10px, 0.9vw, 18px) clamp(12px, 1vw, 20px);
15149
+ display: flex; flex-direction: column;
15150
+ gap: clamp(5px, 0.45vw, 10px);
15151
+ }
15152
+ .ps-cpw-legal-notice-head {
15153
+ display: flex; align-items: center;
15154
+ gap: clamp(6px, 0.5vw, 10px);
15155
+ font-size: clamp(9px, 0.62vw, 11px);
15156
+ font-weight: 700;
15157
+ letter-spacing: 0.14em;
15158
+ text-transform: uppercase;
15159
+ color: var(--ps-accent);
15160
+ }
15161
+ .ps-cpw-legal-notice-head svg {
15162
+ width: clamp(12px, 0.85vw, 15px);
15163
+ height: clamp(12px, 0.85vw, 15px);
15164
+ }
15165
+ .ps-cpw-legal-notice-body {
15166
+ margin: 0;
15167
+ font-size: clamp(10px, 0.7vw, 12.5px);
15168
+ line-height: 1.5;
15169
+ color: var(--ps-text-secondary);
15170
+ }
15171
+
14956
15172
  .ps-cpw-hint {
14957
15173
  font-size: clamp(10px, 0.72vw, 13px);
14958
15174
  line-height: 1.6;
@@ -16870,6 +17086,102 @@ const SKELETON_CONNECTIONS = [
16870
17086
  ["rightHip", "rightKnee"],
16871
17087
  ["rightKnee", "rightAnkle"]
16872
17088
  ];
17089
+ function FaceOverlay({
17090
+ landmarks,
17091
+ imgWidth,
17092
+ imgHeight
17093
+ }) {
17094
+ const W2 = imgWidth;
17095
+ const H2 = imgHeight;
17096
+ const pts = [
17097
+ { key: "forehead", p: landmarks.forehead },
17098
+ { key: "chin", p: landmarks.chin },
17099
+ { key: "noseTip", p: landmarks.noseTip },
17100
+ { key: "noseBridge", p: landmarks.noseBridge },
17101
+ { key: "leftInnerEye", p: landmarks.leftInnerEye },
17102
+ { key: "rightInnerEye", p: landmarks.rightInnerEye },
17103
+ { key: "leftOuterEye", p: landmarks.leftOuterEye },
17104
+ { key: "rightOuterEye", p: landmarks.rightOuterEye },
17105
+ { key: "leftTragus", p: landmarks.leftTragus },
17106
+ { key: "rightTragus", p: landmarks.rightTragus },
17107
+ { key: "leftMouth", p: landmarks.leftMouth },
17108
+ { key: "rightMouth", p: landmarks.rightMouth }
17109
+ ];
17110
+ const connections = [
17111
+ // Face axis
17112
+ [landmarks.forehead, landmarks.noseBridge],
17113
+ [landmarks.noseBridge, landmarks.noseTip],
17114
+ [landmarks.noseTip, landmarks.chin],
17115
+ // Horizontal eye line
17116
+ [landmarks.leftOuterEye, landmarks.leftInnerEye],
17117
+ [landmarks.leftInnerEye, landmarks.rightInnerEye],
17118
+ [landmarks.rightInnerEye, landmarks.rightOuterEye],
17119
+ // Ears
17120
+ [landmarks.leftTragus, landmarks.leftOuterEye],
17121
+ [landmarks.rightOuterEye, landmarks.rightTragus],
17122
+ // Mouth line
17123
+ [landmarks.leftMouth, landmarks.rightMouth]
17124
+ ];
17125
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("svg", { className: "ps-tryon-pose-overlay", viewBox: `0 0 ${W2} ${H2}`, preserveAspectRatio: "xMidYMid meet", children: [
17126
+ connections.map(([a, b], i) => /* @__PURE__ */ jsxRuntimeExports.jsx(
17127
+ "line",
17128
+ {
17129
+ x1: a.x * W2,
17130
+ y1: a.y * H2,
17131
+ x2: b.x * W2,
17132
+ y2: b.y * H2,
17133
+ stroke: "rgba(100,210,255,0.55)",
17134
+ strokeWidth: "3",
17135
+ strokeLinecap: "round",
17136
+ opacity: "0",
17137
+ style: { animation: `ps-pose-fade 0.4s ease ${i * 0.05}s forwards` }
17138
+ },
17139
+ `fl-${i}`
17140
+ )),
17141
+ [landmarks.leftIrisCenter, landmarks.rightIrisCenter].map((c, i) => {
17142
+ const ring = i === 0 ? landmarks.leftIrisRing : landmarks.rightIrisRing;
17143
+ const rx = ring?.length ? Math.abs((ring[0]?.x ?? c.x) - (ring[2]?.x ?? c.x)) * W2 / 2 : 6;
17144
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(
17145
+ "circle",
17146
+ {
17147
+ cx: c.x * W2,
17148
+ cy: c.y * H2,
17149
+ r: Math.max(6, rx),
17150
+ fill: "none",
17151
+ stroke: "rgba(255,230,120,0.95)",
17152
+ strokeWidth: "2.5",
17153
+ opacity: "0",
17154
+ style: { animation: `ps-pose-fade 0.3s ease ${0.3 + i * 0.1}s forwards` }
17155
+ },
17156
+ `iris-${i}`
17157
+ );
17158
+ }),
17159
+ pts.map(({ key, p: p2 }, i) => /* @__PURE__ */ jsxRuntimeExports.jsxs("g", { children: [
17160
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
17161
+ "circle",
17162
+ {
17163
+ cx: p2.x * W2,
17164
+ cy: p2.y * H2,
17165
+ r: "11",
17166
+ fill: "rgba(100,210,255,0.22)",
17167
+ opacity: "0",
17168
+ style: { animation: `ps-pose-fade 0.3s ease ${i * 0.04}s forwards` }
17169
+ }
17170
+ ),
17171
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
17172
+ "circle",
17173
+ {
17174
+ cx: p2.x * W2,
17175
+ cy: p2.y * H2,
17176
+ r: "6",
17177
+ fill: "rgba(100,210,255,0.95)",
17178
+ opacity: "0",
17179
+ style: { animation: `ps-pose-fade 0.3s ease ${i * 0.04}s forwards, ps-dot-pulse 1.5s ease-in-out ${0.5 + i * 0.04}s infinite` }
17180
+ }
17181
+ )
17182
+ ] }, key))
17183
+ ] });
17184
+ }
16873
17185
  function SkeletonOverlay({ landmarks, imgWidth, imgHeight }) {
16874
17186
  const W2 = imgWidth;
16875
17187
  const H2 = imgHeight;
@@ -17734,6 +18046,8 @@ function SizeResultView({
17734
18046
  handleTryOnSubmit,
17735
18047
  tryOnProcessing,
17736
18048
  bodyLandmarks,
18049
+ faceLandmarks = null,
18050
+ measurementType = "body",
17737
18051
  estimationDone = false,
17738
18052
  activeSection,
17739
18053
  setActiveSection,
@@ -18020,28 +18334,33 @@ function SizeResultView({
18020
18334
  onLoad: handleImgLoad
18021
18335
  }
18022
18336
  ),
18023
- bodyLandmarks && /* @__PURE__ */ jsxRuntimeExports.jsx(SkeletonOverlay, { landmarks: bodyLandmarks, imgWidth: imgDims.w, imgHeight: imgDims.h })
18337
+ measurementType === "face" || measurementType === "head" ? faceLandmarks && /* @__PURE__ */ jsxRuntimeExports.jsx(FaceOverlay, { landmarks: faceLandmarks, imgWidth: imgDims.w, imgHeight: imgDims.h }) : bodyLandmarks && /* @__PURE__ */ jsxRuntimeExports.jsx(SkeletonOverlay, { landmarks: bodyLandmarks, imgWidth: imgDims.w, imgHeight: imgDims.h })
18024
18338
  ] }),
18025
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-tryon-sr-right-col ps-tryon-snap-steps", children: [
18026
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: `ps-tryon-snap-step${bodyLandmarks ? " ps-done" : " ps-active"}`, children: [
18027
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-tryon-snap-step-icon", children: bodyLandmarks ? /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "ps-tryon-snap-check", children: "✓" }) : /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-tryon-size-loading-spinner", style: { width: "1vw", height: "1vw", borderWidth: "1.5px" } }) }),
18028
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: t2("Detecting body pose") })
18029
- ] }),
18030
- !sizingDone && /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
18031
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: `ps-tryon-snap-step${analyzingDone ? " ps-done" : bodyLandmarks ? " ps-active" : ""}`, children: [
18032
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-tryon-snap-step-icon", children: !bodyLandmarks ? /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "ps-tryon-snap-num", children: "2" }) : !analyzingDone ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-tryon-size-loading-spinner", style: { width: "1vw", height: "1vw", borderWidth: "1.5px" } }) : /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "ps-tryon-snap-check", children: "✓" }) }),
18033
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: t2("Analyzing your size") })
18339
+ (() => {
18340
+ const isFaceCategory = measurementType === "face" || measurementType === "head";
18341
+ const detectionDone = isFaceCategory ? !!faceLandmarks : !!bodyLandmarks;
18342
+ const detectLabel = isFaceCategory ? measurementType === "head" ? t2("Detecting head") : t2("Detecting face") : t2("Detecting body pose");
18343
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-tryon-sr-right-col ps-tryon-snap-steps", children: [
18344
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: `ps-tryon-snap-step${detectionDone ? " ps-done" : " ps-active"}`, children: [
18345
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-tryon-snap-step-icon", children: detectionDone ? /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "ps-tryon-snap-check", children: "✓" }) : /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-tryon-size-loading-spinner", style: { width: "1vw", height: "1vw", borderWidth: "1.5px" } }) }),
18346
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: detectLabel })
18034
18347
  ] }),
18035
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: `ps-tryon-snap-step${analyzingDone ? " ps-active" : ""}`, children: [
18036
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-tryon-snap-step-icon", children: !analyzingDone ? /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "ps-tryon-snap-num", children: "3" }) : /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-tryon-size-loading-spinner", style: { width: "1vw", height: "1vw", borderWidth: "1.5px" } }) }),
18037
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: t2("Finding best fit for you") })
18348
+ !sizingDone && /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
18349
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: `ps-tryon-snap-step${analyzingDone ? " ps-done" : detectionDone ? " ps-active" : ""}`, children: [
18350
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-tryon-snap-step-icon", children: !detectionDone ? /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "ps-tryon-snap-num", children: "2" }) : !analyzingDone ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-tryon-size-loading-spinner", style: { width: "1vw", height: "1vw", borderWidth: "1.5px" } }) : /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "ps-tryon-snap-check", children: "✓" }) }),
18351
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: t2("Analyzing your size") })
18352
+ ] }),
18353
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: `ps-tryon-snap-step${analyzingDone ? " ps-active" : ""}`, children: [
18354
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-tryon-snap-step-icon", children: !analyzingDone ? /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "ps-tryon-snap-num", children: "3" }) : /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-tryon-size-loading-spinner", style: { width: "1vw", height: "1vw", borderWidth: "1.5px" } }) }),
18355
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: t2("Finding best fit for you") })
18356
+ ] })
18357
+ ] }),
18358
+ tryOnProcessing && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: `ps-tryon-snap-step${tryOnDone ? " ps-done" : " ps-active"}`, children: [
18359
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-tryon-snap-step-icon", children: tryOnDone ? /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "ps-tryon-snap-check", children: "✓" }) : /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-tryon-size-loading-spinner", style: { width: "1vw", height: "1vw", borderWidth: "1.5px" } }) }),
18360
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: t2("Generating virtual try-on") })
18038
18361
  ] })
18039
- ] }),
18040
- tryOnProcessing && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: `ps-tryon-snap-step${tryOnDone ? " ps-done" : " ps-active"}`, children: [
18041
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-tryon-snap-step-icon", children: tryOnDone ? /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "ps-tryon-snap-check", children: "✓" }) : /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-tryon-size-loading-spinner", style: { width: "1vw", height: "1vw", borderWidth: "1.5px" } }) }),
18042
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: t2("Generating virtual try-on") })
18043
- ] })
18044
- ] })
18362
+ ] });
18363
+ })()
18045
18364
  ] }),
18046
18365
  (allDone || sizingResult && !isSnapProcessing) && /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
18047
18366
  isMultiSection ? activeSection ? (
@@ -18840,9 +19159,18 @@ function CreateProfileWizard({ onSave, onCancel, onPhotoPreview, onEstimate, t:
18840
19159
  const photoInputRef = reactExports.useRef(null);
18841
19160
  const nameInputRef = reactExports.useRef(null);
18842
19161
  const [nameShaking, setNameShaking] = reactExports.useState(false);
19162
+ const [ageConfirmed, setAgeConfirmed] = reactExports.useState(null);
19163
+ const openFilePicker = () => {
19164
+ if (ageConfirmed !== true) return;
19165
+ photoInputRef.current?.click();
19166
+ };
18843
19167
  const handlePhotoSelect = async (e) => {
18844
19168
  const file = e.target.files?.[0];
18845
19169
  if (!file) return;
19170
+ if (ageConfirmed !== true) {
19171
+ setError(t2("Please confirm that the person in the photo is 18 or older before uploading."));
19172
+ return;
19173
+ }
18846
19174
  if (!file.type.startsWith("image/")) {
18847
19175
  setError(t2("Please upload an image file"));
18848
19176
  return;
@@ -19423,18 +19751,71 @@ function CreateProfileWizard({ onSave, onCancel, onPhotoPreview, onEstimate, t:
19423
19751
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-cpw-image-left", children: photoBase64 ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-cpw-photo-preview-frame", children: [
19424
19752
  /* @__PURE__ */ jsxRuntimeExports.jsx("img", { src: photoBase64, alt: t2("Profile photo"), className: "ps-cpw-photo-preview-img" }),
19425
19753
  /* @__PURE__ */ jsxRuntimeExports.jsx("button", { type: "button", className: "ps-cpw-photo-remove", onClick: handleRemovePhoto, "aria-label": t2("Remove photo"), children: "×" }),
19426
- /* @__PURE__ */ jsxRuntimeExports.jsx("button", { type: "button", className: "ps-cpw-photo-retake-pill", onClick: () => photoInputRef.current?.click(), children: t2("Retake") })
19427
- ] }) : /* @__PURE__ */ jsxRuntimeExports.jsxs("button", { type: "button", className: "ps-cpw-dropzone", onClick: () => photoInputRef.current?.click(), disabled: photoUploading, children: [
19428
- /* @__PURE__ */ jsxRuntimeExports.jsx("img", { src: photoUploadIllustrationImg, alt: "", "aria-hidden": "true", className: "ps-cpw-dropzone-silhouette" }),
19429
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-cpw-dropzone-content", children: [
19430
- /* @__PURE__ */ jsxRuntimeExports.jsxs("svg", { className: "ps-cpw-dropzone-upload-icon", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: [
19431
- /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" }),
19432
- /* @__PURE__ */ jsxRuntimeExports.jsx("polyline", { points: "17 8 12 3 7 8" }),
19433
- /* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: "12", y1: "3", x2: "12", y2: "15" })
19434
- ] }),
19435
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "ps-cpw-dropzone-title", children: photoUploading ? t2("Processing...") : t2("Drop a photo or click to upload") }),
19436
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "ps-cpw-dropzone-hint", children: t2("JPEG · PNG · WebP · up to 10MB") })
19437
- ] })
19754
+ /* @__PURE__ */ jsxRuntimeExports.jsx("button", { type: "button", className: "ps-cpw-photo-retake-pill", onClick: openFilePicker, children: t2("Retake") })
19755
+ ] }) : /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-cpw-dropzone-wrap", children: [
19756
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
19757
+ "button",
19758
+ {
19759
+ type: "button",
19760
+ className: `ps-cpw-dropzone${ageConfirmed !== true ? " ps-cpw-dropzone-blurred" : ""}`,
19761
+ onClick: openFilePicker,
19762
+ disabled: photoUploading || ageConfirmed !== true,
19763
+ "aria-hidden": ageConfirmed !== true,
19764
+ tabIndex: ageConfirmed !== true ? -1 : 0,
19765
+ children: [
19766
+ /* @__PURE__ */ jsxRuntimeExports.jsx("img", { src: photoUploadIllustrationImg, alt: "", "aria-hidden": "true", className: "ps-cpw-dropzone-silhouette" }),
19767
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-cpw-dropzone-content", children: [
19768
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("svg", { className: "ps-cpw-dropzone-upload-icon", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: [
19769
+ /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" }),
19770
+ /* @__PURE__ */ jsxRuntimeExports.jsx("polyline", { points: "17 8 12 3 7 8" }),
19771
+ /* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: "12", y1: "3", x2: "12", y2: "15" })
19772
+ ] }),
19773
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "ps-cpw-dropzone-title", children: photoUploading ? t2("Processing...") : t2("Drop a photo or click to upload") }),
19774
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "ps-cpw-dropzone-hint", children: t2("JPEG · PNG · WebP · up to 10MB") })
19775
+ ] })
19776
+ ]
19777
+ }
19778
+ ),
19779
+ ageConfirmed === null && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-cpw-age-gate", role: "dialog", "aria-labelledby": "ps-cpw-age-gate-q", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-cpw-age-gate-card", children: [
19780
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-cpw-age-gate-eyebrow", children: t2("AGE VERIFICATION") }),
19781
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { id: "ps-cpw-age-gate-q", className: "ps-cpw-age-gate-question", children: t2("Is the person in this photo 18 years or older?") }),
19782
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-cpw-age-gate-actions", children: [
19783
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
19784
+ "button",
19785
+ {
19786
+ type: "button",
19787
+ className: "ps-cpw-age-gate-btn ps-cpw-age-gate-btn-primary",
19788
+ onClick: () => {
19789
+ setAgeConfirmed(true);
19790
+ setError("");
19791
+ },
19792
+ children: t2("Yes, 18 or older")
19793
+ }
19794
+ ),
19795
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
19796
+ "button",
19797
+ {
19798
+ type: "button",
19799
+ className: "ps-cpw-age-gate-btn ps-cpw-age-gate-btn-secondary",
19800
+ onClick: () => setAgeConfirmed(false),
19801
+ children: t2("No, under 18")
19802
+ }
19803
+ )
19804
+ ] })
19805
+ ] }) }),
19806
+ ageConfirmed === false && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-cpw-age-gate", role: "alert", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-cpw-age-gate-card ps-cpw-age-gate-card-blocked", children: [
19807
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-cpw-age-gate-eyebrow ps-cpw-age-gate-eyebrow-blocked", children: t2("UPLOAD NOT ALLOWED") }),
19808
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-cpw-age-gate-question", children: t2("For your safety, we cannot process photos of people under 18.") }),
19809
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
19810
+ "button",
19811
+ {
19812
+ type: "button",
19813
+ className: "ps-cpw-age-gate-btn ps-cpw-age-gate-btn-secondary",
19814
+ onClick: () => setAgeConfirmed(null),
19815
+ children: t2("Go back")
19816
+ }
19817
+ )
19818
+ ] }) })
19438
19819
  ] }) }),
19439
19820
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-cpw-image-right", children: [
19440
19821
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-bp-inline-fields ps-cpw-inline-fields", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-bp-inline-row", children: [
@@ -19465,6 +19846,13 @@ function CreateProfileWizard({ onSave, onCancel, onPhotoPreview, onEstimate, t:
19465
19846
  ] })
19466
19847
  ] })
19467
19848
  ] }),
19849
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-cpw-legal-notice", children: [
19850
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-cpw-legal-notice-head", children: [
19851
+ /* @__PURE__ */ jsxRuntimeExports.jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z" }) }),
19852
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: t2("LEGAL NOTICE") })
19853
+ ] }),
19854
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "ps-cpw-legal-notice-body", children: t2("Your image will be used to generate a virtual try-on preview showing how selected items may look and fit. Images are processed securely and are not stored after generation.") })
19855
+ ] }),
19468
19856
  error && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-cpw-error", children: error })
19469
19857
  ] })
19470
19858
  ] }, "image-photo"),
@@ -20308,6 +20696,8 @@ function PhotoStepMobile({
20308
20696
  const isCloseUp = photoVariant === "close-up";
20309
20697
  const fileRef = reactExports.useRef(null);
20310
20698
  const hasPhoto = !!photoPreview;
20699
+ const [ageConfirmed, setAgeConfirmed] = reactExports.useState(null);
20700
+ const gated = !hasPhoto && ageConfirmed !== true;
20311
20701
  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-pm-root", children: [
20312
20702
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-pm-header", children: [
20313
20703
  /* @__PURE__ */ jsxRuntimeExports.jsx("h2", { className: "ps-pm-title", children: isCloseUp ? t2("Upload a face photo or selfie") : t2("Review your photo") }),
@@ -20335,19 +20725,46 @@ function PhotoStepMobile({
20335
20725
  children: /* @__PURE__ */ jsxRuntimeExports.jsx(CloseIconSm, {})
20336
20726
  }
20337
20727
  )
20338
- ] }) : /* @__PURE__ */ jsxRuntimeExports.jsxs(
20339
- "button",
20340
- {
20341
- type: "button",
20342
- className: "ps-pm-preview-empty",
20343
- onClick: () => fileRef.current?.click(),
20344
- children: [
20345
- /* @__PURE__ */ jsxRuntimeExports.jsx(UploadIconLg, {}),
20346
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "ps-pm-preview-empty-title", children: t2("Tap to upload") }),
20347
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "ps-pm-preview-empty-hint", children: t2("JPEG, PNG up to 10MB") })
20348
- ]
20349
- }
20350
- ) }),
20728
+ ] }) : /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
20729
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
20730
+ "button",
20731
+ {
20732
+ type: "button",
20733
+ className: `ps-pm-preview-empty${gated ? " ps-pm-preview-blurred" : ""}`,
20734
+ onClick: () => {
20735
+ if (!gated) fileRef.current?.click();
20736
+ },
20737
+ disabled: gated,
20738
+ "aria-hidden": gated,
20739
+ tabIndex: gated ? -1 : 0,
20740
+ children: [
20741
+ /* @__PURE__ */ jsxRuntimeExports.jsx(UploadIconLg, {}),
20742
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "ps-pm-preview-empty-title", children: t2("Tap to upload") }),
20743
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "ps-pm-preview-empty-hint", children: t2("JPEG, PNG up to 10MB") })
20744
+ ]
20745
+ }
20746
+ ),
20747
+ ageConfirmed === null && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-pm-age-gate", role: "dialog", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-pm-age-gate-card", children: [
20748
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-pm-age-gate-eyebrow", children: t2("AGE VERIFICATION") }),
20749
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-pm-age-gate-question", children: t2("Is the person in this photo 18 years or older?") }),
20750
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-pm-age-gate-actions", children: [
20751
+ /* @__PURE__ */ jsxRuntimeExports.jsx("button", { type: "button", className: "ps-pm-age-gate-btn ps-pm-age-gate-btn-primary", onClick: () => setAgeConfirmed(true), children: t2("Yes, 18 or older") }),
20752
+ /* @__PURE__ */ jsxRuntimeExports.jsx("button", { type: "button", className: "ps-pm-age-gate-btn ps-pm-age-gate-btn-secondary", onClick: () => setAgeConfirmed(false), children: t2("No, under 18") })
20753
+ ] })
20754
+ ] }) }),
20755
+ ageConfirmed === false && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-pm-age-gate", role: "alert", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-pm-age-gate-card", children: [
20756
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-pm-age-gate-eyebrow ps-pm-age-gate-eyebrow-blocked", children: t2("UPLOAD NOT ALLOWED") }),
20757
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-pm-age-gate-question", children: t2("For your safety, we cannot process photos of people under 18.") }),
20758
+ /* @__PURE__ */ jsxRuntimeExports.jsx("button", { type: "button", className: "ps-pm-age-gate-btn ps-pm-age-gate-btn-secondary", onClick: () => setAgeConfirmed(null), children: t2("Go back") })
20759
+ ] }) })
20760
+ ] }) }),
20761
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-pm-legal-notice", children: [
20762
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-pm-legal-notice-head", children: [
20763
+ /* @__PURE__ */ jsxRuntimeExports.jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z" }) }),
20764
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: t2("LEGAL NOTICE") })
20765
+ ] }),
20766
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "ps-pm-legal-notice-body", children: t2("Your image will be used to generate a virtual try-on preview showing how selected items may look and fit. Images are processed securely and are not stored after generation.") })
20767
+ ] }),
20351
20768
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-pm-checklist", children: [
20352
20769
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-pm-checklist-icon", children: /* @__PURE__ */ jsxRuntimeExports.jsx(InfoIcon, {}) }),
20353
20770
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-pm-checklist-body", children: [
@@ -21869,6 +22286,7 @@ function PrimeStyleTryonInner({
21869
22286
  const bodyRef = reactExports.useRef(null);
21870
22287
  const modelPoseRef = reactExports.useRef(null);
21871
22288
  const [bodyLandmarks, setBodyLandmarks] = reactExports.useState(null);
22289
+ const [faceLandmarks, setFaceLandmarks] = reactExports.useState(null);
21872
22290
  const selectedFileRef = reactExports.useRef(null);
21873
22291
  reactExports.useEffect(() => {
21874
22292
  try {
@@ -22449,8 +22867,10 @@ function PrimeStyleTryonInner({
22449
22867
  setView("size-result");
22450
22868
  const measurementType = detectMeasurementType(productTitle);
22451
22869
  if (measurementType === "face" || measurementType === "head") {
22870
+ setFaceLandmarks(null);
22452
22871
  try {
22453
22872
  const faceResult = await detectFaceMeasurements(objUrl);
22873
+ if (faceResult) setFaceLandmarks(faceResult.landmarks);
22454
22874
  const facePayload = {
22455
22875
  product: { title: productTitle },
22456
22876
  sizeGuide: sizeGuide ?? { found: false },
@@ -23104,6 +23524,8 @@ function PrimeStyleTryonInner({
23104
23524
  handleTryOnSubmit,
23105
23525
  tryOnProcessing,
23106
23526
  bodyLandmarks,
23527
+ faceLandmarks,
23528
+ measurementType: detectMeasurementType(productTitle),
23107
23529
  activeSection,
23108
23530
  setActiveSection,
23109
23531
  onResetTryOn: () => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@primestyleai/tryon",
3
- "version": "5.8.39",
3
+ "version": "5.8.40",
4
4
  "description": "PrimeStyle Virtual Try-On SDK — React component & Web Component",
5
5
  "type": "module",
6
6
  "main": "dist/primestyle-tryon.js",