@primestyleai/tryon 5.8.38 → 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.
@@ -17,11 +17,11 @@ const LEFT_ANKLE = 27;
17
17
  const RIGHT_ANKLE = 28;
18
18
  const NOSE = 0;
19
19
  let poseLandmarker = null;
20
- let loadingPromise = null;
20
+ let loadingPromise$1 = null;
21
21
  async function loadMediaPipe() {
22
22
  if (poseLandmarker) return;
23
- if (loadingPromise) return loadingPromise;
24
- loadingPromise = (async () => {
23
+ if (loadingPromise$1) return loadingPromise$1;
24
+ loadingPromise$1 = (async () => {
25
25
  const vision = await import(
26
26
  /* webpackIgnore: true */
27
27
  // @ts-ignore dynamic CDN import
@@ -40,12 +40,12 @@ async function loadMediaPipe() {
40
40
  numPoses: 1
41
41
  });
42
42
  })();
43
- return loadingPromise;
43
+ return loadingPromise$1;
44
44
  }
45
45
  async function detectMeasurementLines(imageSrc) {
46
46
  try {
47
47
  await loadMediaPipe();
48
- const img = await loadImage(imageSrc);
48
+ const img = await loadImage$1(imageSrc);
49
49
  const result = poseLandmarker.detect(img);
50
50
  if (!result?.landmarks?.length || result.landmarks[0].length < 25) {
51
51
  return null;
@@ -80,7 +80,7 @@ async function detectMeasurementLines(imageSrc) {
80
80
  return null;
81
81
  }
82
82
  }
83
- function loadImage(src) {
83
+ function loadImage$1(src) {
84
84
  return new Promise((resolve, reject) => {
85
85
  const img = new Image();
86
86
  img.crossOrigin = "anonymous";
@@ -99,7 +99,7 @@ async function detectBodyLandmarks(imageSrc) {
99
99
  await loadMediaPipe();
100
100
  let img;
101
101
  if (typeof imageSrc === "string") {
102
- img = await loadImage(imageSrc);
102
+ img = await loadImage$1(imageSrc);
103
103
  } else {
104
104
  img = imageSrc;
105
105
  }
@@ -128,6 +128,165 @@ async function detectBodyLandmarks(imageSrc) {
128
128
  return null;
129
129
  }
130
130
  }
131
+ const IDX = {
132
+ noseTip: 1,
133
+ noseBridge: 168,
134
+ leftInnerEye: 133,
135
+ rightInnerEye: 362,
136
+ leftOuterEye: 33,
137
+ rightOuterEye: 263,
138
+ // Iris landmarks (478-point model with iris refinement)
139
+ leftIrisCenter: 468,
140
+ leftIrisRing: [469, 470, 471, 472],
141
+ rightIrisCenter: 473,
142
+ rightIrisRing: [474, 475, 476, 477],
143
+ // Tragus (ear attach point) — best approximations in the face mesh
144
+ leftTragus: 234,
145
+ rightTragus: 454,
146
+ forehead: 10,
147
+ chin: 152,
148
+ leftMouth: 61,
149
+ rightMouth: 291
150
+ };
151
+ const IRIS_DIAMETER_MM = 11.7;
152
+ let faceLandmarker = null;
153
+ let loadingPromise = null;
154
+ async function loadFaceLandmarker() {
155
+ if (faceLandmarker) return;
156
+ if (loadingPromise) return loadingPromise;
157
+ loadingPromise = (async () => {
158
+ const vision = await import(
159
+ /* webpackIgnore: true */
160
+ // @ts-ignore dynamic CDN import
161
+ "https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@0.10.33/vision_bundle.mjs"
162
+ );
163
+ const { FilesetResolver, FaceLandmarker } = vision;
164
+ const filesetResolver = await FilesetResolver.forVisionTasks(
165
+ "https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@0.10.33/wasm"
166
+ );
167
+ faceLandmarker = await FaceLandmarker.createFromOptions(filesetResolver, {
168
+ baseOptions: {
169
+ modelAssetPath: "https://storage.googleapis.com/mediapipe-models/face_landmarker/face_landmarker/float16/latest/face_landmarker.task",
170
+ delegate: "GPU"
171
+ },
172
+ runningMode: "IMAGE",
173
+ numFaces: 1,
174
+ outputFaceBlendshapes: false,
175
+ outputFacialTransformationMatrixes: false
176
+ });
177
+ })();
178
+ return loadingPromise;
179
+ }
180
+ function loadImage(src) {
181
+ return new Promise((resolve, reject) => {
182
+ const img = new Image();
183
+ img.crossOrigin = "anonymous";
184
+ img.onload = () => resolve(img);
185
+ img.onerror = () => {
186
+ const img2 = new Image();
187
+ img2.onload = () => resolve(img2);
188
+ img2.onerror = () => reject(new Error("Failed to load image"));
189
+ img2.src = src;
190
+ };
191
+ img.src = src;
192
+ });
193
+ }
194
+ function irisDiameterPx(ring, imageWidth, imageHeight) {
195
+ if (ring.length < 4) return 0;
196
+ const toPx = (p) => ({ x: p.x * imageWidth, y: p.y * imageHeight });
197
+ const [p0, p1, p2, p3] = ring.map(toPx);
198
+ const d1 = Math.hypot(p0.x - p2.x, p0.y - p2.y);
199
+ const d2 = Math.hypot(p1.x - p3.x, p1.y - p3.y);
200
+ return (d1 + d2) / 2;
201
+ }
202
+ function extractLandmarks(raw) {
203
+ if (raw.length < 478) return null;
204
+ const at = (i) => ({ x: raw[i].x, y: raw[i].y, z: raw[i].z });
205
+ return {
206
+ noseTip: at(IDX.noseTip),
207
+ noseBridge: at(IDX.noseBridge),
208
+ leftInnerEye: at(IDX.leftInnerEye),
209
+ rightInnerEye: at(IDX.rightInnerEye),
210
+ leftOuterEye: at(IDX.leftOuterEye),
211
+ rightOuterEye: at(IDX.rightOuterEye),
212
+ leftIrisCenter: at(IDX.leftIrisCenter),
213
+ rightIrisCenter: at(IDX.rightIrisCenter),
214
+ leftIrisRing: IDX.leftIrisRing.map(at),
215
+ rightIrisRing: IDX.rightIrisRing.map(at),
216
+ leftTragus: at(IDX.leftTragus),
217
+ rightTragus: at(IDX.rightTragus),
218
+ forehead: at(IDX.forehead),
219
+ chin: at(IDX.chin),
220
+ leftMouth: at(IDX.leftMouth),
221
+ rightMouth: at(IDX.rightMouth)
222
+ };
223
+ }
224
+ function computeMeasurements(lm, imageWidth, imageHeight) {
225
+ const leftPx = irisDiameterPx(lm.leftIrisRing, imageWidth, imageHeight);
226
+ const rightPx = irisDiameterPx(lm.rightIrisRing, imageWidth, imageHeight);
227
+ const irisPx = (leftPx + rightPx) / 2;
228
+ let irisConfidence = 1;
229
+ if (irisPx < 8) irisConfidence = 0.3;
230
+ else if (Math.abs(leftPx - rightPx) / irisPx > 0.3) irisConfidence = 0.5;
231
+ else if (Math.abs(leftPx - rightPx) / irisPx > 0.15) irisConfidence = 0.8;
232
+ const pxToMm = irisPx > 0 ? IRIS_DIAMETER_MM / irisPx : 0;
233
+ const mmBetween = (a2, b2) => {
234
+ const pxDist = Math.hypot(
235
+ (a2.x - b2.x) * imageWidth,
236
+ (a2.y - b2.y) * imageHeight
237
+ );
238
+ return pxDist * pxToMm;
239
+ };
240
+ const pd = mmBetween(lm.leftIrisCenter, lm.rightIrisCenter);
241
+ const bridgeWidth = mmBetween(lm.leftInnerEye, lm.rightInnerEye);
242
+ const faceWidth = mmBetween(lm.leftTragus, lm.rightTragus);
243
+ const templeLengthLeft = mmBetween(lm.leftTragus, lm.leftOuterEye);
244
+ const templeLengthRight = mmBetween(lm.rightTragus, lm.rightOuterEye);
245
+ const templeLength = (templeLengthLeft + templeLengthRight) / 2;
246
+ const headWidth = faceWidth * 1.07;
247
+ const zDepthNorm = Math.abs((lm.forehead.z ?? 0) - (lm.chin.z ?? 0));
248
+ const rawHeadDepthMm = zDepthNorm * imageWidth * pxToMm;
249
+ const headDepth = Math.max(170, Math.min(210, rawHeadDepthMm || 190));
250
+ const a = headWidth / 2;
251
+ const b = headDepth / 2;
252
+ const headCircumference = Math.PI * Math.sqrt(2 * (a * a + b * b));
253
+ return {
254
+ measurements: {
255
+ irisDiameter: IRIS_DIAMETER_MM,
256
+ pd: round1(pd),
257
+ bridgeWidth: round1(bridgeWidth),
258
+ faceWidth: round1(faceWidth),
259
+ templeLengthLeft: round1(templeLengthLeft),
260
+ templeLengthRight: round1(templeLengthRight),
261
+ templeLength: round1(templeLength),
262
+ headWidth: round1(headWidth),
263
+ headDepth: round1(headDepth),
264
+ headCircumference: round1(headCircumference)
265
+ },
266
+ irisConfidence
267
+ };
268
+ }
269
+ function round1(n) {
270
+ return Math.round(n * 10) / 10;
271
+ }
272
+ async function detectFaceMeasurements(imageSrc) {
273
+ try {
274
+ await loadFaceLandmarker();
275
+ const img = typeof imageSrc === "string" ? await loadImage(imageSrc) : imageSrc;
276
+ const result = faceLandmarker.detect(img);
277
+ if (!result?.faceLandmarks?.length) return null;
278
+ const raw = result.faceLandmarks[0];
279
+ const landmarks = extractLandmarks(raw);
280
+ if (!landmarks) return null;
281
+ const imageWidth = img.naturalWidth || img.width;
282
+ const imageHeight = img.naturalHeight || img.height;
283
+ const { measurements, irisConfidence } = computeMeasurements(landmarks, imageWidth, imageHeight);
284
+ return { landmarks, measurementsMm: measurements, irisConfidence, imageWidth, imageHeight };
285
+ } catch (err) {
286
+ console.error("[PS-SDK] Face detection failed:", err);
287
+ return null;
288
+ }
289
+ }
131
290
  function parseRange(s) {
132
291
  const ns = s.replace(/[^\d.\-–]/g, " ").trim().split(/[\s\-–]+/).filter(Boolean).map(Number).filter((n) => !isNaN(n));
133
292
  return ns.length ? { min: Math.min(...ns), max: Math.max(...ns) } : { min: 0, max: 0 };
@@ -3509,6 +3668,101 @@ const STYLES = `
3509
3668
  }
3510
3669
  .ps-pm-preview-remove:hover { background: #FFFFFF; }
3511
3670
 
3671
+ /* ── Mobile age-gate overlay ── */
3672
+ .ps-pm-preview-blurred {
3673
+ filter: blur(6px) saturate(0.7);
3674
+ pointer-events: none;
3675
+ user-select: none;
3676
+ }
3677
+ .ps-pm-age-gate {
3678
+ position: absolute; inset: 0;
3679
+ display: flex; align-items: center; justify-content: center;
3680
+ padding: max(16px, 4vw);
3681
+ background: rgba(255, 255, 255, 0.55);
3682
+ backdrop-filter: blur(8px);
3683
+ -webkit-backdrop-filter: blur(8px);
3684
+ z-index: 2;
3685
+ animation: ps-pm-age-gate-in 0.28s ease-out both;
3686
+ }
3687
+ @keyframes ps-pm-age-gate-in {
3688
+ 0% { opacity: 0; transform: scale(0.97); }
3689
+ 100% { opacity: 1; transform: scale(1); }
3690
+ }
3691
+ .ps-pm-age-gate-card {
3692
+ width: 100%; max-width: max(280px, 82vw);
3693
+ padding: max(18px, 4.6vw) max(16px, 4.2vw);
3694
+ background: #FFFFFF;
3695
+ border: 1px solid var(--ps-border-subtle);
3696
+ border-radius: max(12px, 3vw);
3697
+ box-shadow: 0 20px 40px -12px rgba(17, 24, 39, 0.25),
3698
+ 0 8px 16px -8px rgba(17, 24, 39, 0.15);
3699
+ display: flex; flex-direction: column;
3700
+ align-items: center; text-align: center;
3701
+ gap: max(8px, 2vw);
3702
+ }
3703
+ .ps-pm-age-gate-eyebrow {
3704
+ font-size: max(10px, 2.6vw); font-weight: 700;
3705
+ letter-spacing: 0.14em; text-transform: uppercase;
3706
+ color: var(--ps-accent);
3707
+ }
3708
+ .ps-pm-age-gate-eyebrow-blocked { color: #C02626; }
3709
+ .ps-pm-age-gate-question {
3710
+ font-size: max(14px, 3.8vw); font-weight: 600;
3711
+ line-height: 1.35; color: var(--ps-text-primary); margin: 0;
3712
+ }
3713
+ .ps-pm-age-gate-actions {
3714
+ display: flex; flex-direction: column; gap: max(8px, 2vw);
3715
+ width: 100%; margin-top: max(4px, 1vw);
3716
+ }
3717
+ .ps-pm-age-gate-btn {
3718
+ width: 100%;
3719
+ padding: max(11px, 2.9vw) max(14px, 3.6vw);
3720
+ border-radius: 999px;
3721
+ font-family: inherit;
3722
+ font-size: max(12px, 3.2vw); font-weight: 700;
3723
+ letter-spacing: 0.02em;
3724
+ cursor: pointer;
3725
+ transition: background 0.18s, border-color 0.18s, color 0.18s;
3726
+ }
3727
+ .ps-pm-age-gate-btn-primary {
3728
+ background: var(--ps-accent);
3729
+ color: #FFFFFF;
3730
+ border: 1.5px solid var(--ps-accent);
3731
+ }
3732
+ .ps-pm-age-gate-btn-secondary {
3733
+ background: transparent;
3734
+ color: var(--ps-text-primary);
3735
+ border: 1.5px solid var(--ps-border-color);
3736
+ }
3737
+ .ps-pm-age-gate-btn-secondary:active {
3738
+ background: var(--ps-bg-secondary);
3739
+ }
3740
+
3741
+ /* ── Mobile legal notice ── */
3742
+ .ps-pm-legal-notice {
3743
+ margin-top: max(10px, 2.6vw);
3744
+ background: rgba(33, 84, 239, 0.04);
3745
+ border: 1px solid rgba(33, 84, 239, 0.16);
3746
+ border-radius: max(10px, 2.6vw);
3747
+ padding: max(10px, 2.6vw) max(12px, 3.1vw);
3748
+ display: flex; flex-direction: column;
3749
+ gap: max(4px, 1vw);
3750
+ }
3751
+ .ps-pm-legal-notice-head {
3752
+ display: flex; align-items: center;
3753
+ gap: max(6px, 1.5vw);
3754
+ font-size: max(10px, 2.6vw); font-weight: 700;
3755
+ letter-spacing: 0.12em; text-transform: uppercase;
3756
+ color: var(--ps-accent);
3757
+ }
3758
+ .ps-pm-legal-notice-head svg { width: max(13px, 3.4vw); height: max(13px, 3.4vw); }
3759
+ .ps-pm-legal-notice-body {
3760
+ margin: 0;
3761
+ font-size: max(11px, 2.9vw);
3762
+ line-height: 1.5;
3763
+ color: var(--ps-text-secondary);
3764
+ }
3765
+
3512
3766
  /* Checklist for accuracy card */
3513
3767
  .ps-pm-checklist {
3514
3768
  display: flex; gap: max(12px, 3.1vw);
@@ -5370,6 +5624,127 @@ const STYLES = `
5370
5624
  transition: background 0.18s;
5371
5625
  }
5372
5626
  .ps-cpw-photo-retake:hover { background: rgba(33, 84, 239, 0.08); }
5627
+
5628
+ /* ── Age-gate overlay on the dropzone ──
5629
+ Dropzone stays visible but blurred; overlay shows a premium card
5630
+ with the 18+ confirmation question and two pill buttons. */
5631
+ .ps-cpw-dropzone-wrap {
5632
+ position: relative;
5633
+ flex: 1; min-height: 0;
5634
+ width: 100%; box-sizing: border-box;
5635
+ display: flex; flex-direction: column;
5636
+ }
5637
+ .ps-cpw-dropzone-blurred {
5638
+ filter: blur(6px) saturate(0.7);
5639
+ pointer-events: none;
5640
+ user-select: none;
5641
+ }
5642
+ .ps-cpw-age-gate {
5643
+ position: absolute; inset: 0;
5644
+ display: flex; align-items: center; justify-content: center;
5645
+ padding: clamp(12px, 1vw, 24px);
5646
+ border-radius: clamp(10px, 0.75vw, 16px);
5647
+ background: rgba(255, 255, 255, 0.55);
5648
+ backdrop-filter: blur(8px);
5649
+ -webkit-backdrop-filter: blur(8px);
5650
+ z-index: 2;
5651
+ animation: ps-cpw-age-gate-in 0.28s ease-out both;
5652
+ }
5653
+ @keyframes ps-cpw-age-gate-in {
5654
+ 0% { opacity: 0; transform: scale(0.97); }
5655
+ 100% { opacity: 1; transform: scale(1); }
5656
+ }
5657
+ .ps-cpw-age-gate-card {
5658
+ width: 100%; max-width: clamp(240px, 24vw, 420px);
5659
+ padding: clamp(16px, 1.4vw, 28px) clamp(18px, 1.6vw, 32px);
5660
+ background: #FFFFFF;
5661
+ border: 1px solid var(--ps-border-subtle);
5662
+ border-radius: clamp(10px, 0.9vw, 18px);
5663
+ box-shadow: 0 20px 40px -12px rgba(17, 24, 39, 0.25),
5664
+ 0 8px 16px -8px rgba(17, 24, 39, 0.15);
5665
+ display: flex; flex-direction: column;
5666
+ align-items: center; text-align: center;
5667
+ gap: clamp(8px, 0.7vw, 14px);
5668
+ }
5669
+ .ps-cpw-age-gate-eyebrow {
5670
+ font-size: clamp(9px, 0.62vw, 11px);
5671
+ font-weight: 700;
5672
+ letter-spacing: 0.18em;
5673
+ text-transform: uppercase;
5674
+ color: var(--ps-accent);
5675
+ }
5676
+ .ps-cpw-age-gate-eyebrow-blocked { color: #C02626; }
5677
+ .ps-cpw-age-gate-question {
5678
+ font-size: clamp(13px, 0.95vw, 18px);
5679
+ font-weight: 600;
5680
+ line-height: 1.35;
5681
+ color: var(--ps-text-primary);
5682
+ margin: 0;
5683
+ }
5684
+ .ps-cpw-age-gate-actions {
5685
+ display: flex; gap: clamp(8px, 0.65vw, 14px);
5686
+ width: 100%;
5687
+ margin-top: clamp(4px, 0.35vw, 8px);
5688
+ }
5689
+ .ps-cpw-age-gate-btn {
5690
+ flex: 1;
5691
+ padding: clamp(9px, 0.75vw, 14px) clamp(12px, 1vw, 18px);
5692
+ border-radius: 999px;
5693
+ font-family: inherit;
5694
+ font-size: clamp(11px, 0.78vw, 14px);
5695
+ font-weight: 700;
5696
+ letter-spacing: 0.02em;
5697
+ cursor: pointer;
5698
+ transition: transform 0.18s, background 0.18s, border-color 0.18s, color 0.18s;
5699
+ }
5700
+ .ps-cpw-age-gate-btn:hover { transform: translateY(-1px); }
5701
+ .ps-cpw-age-gate-btn-primary {
5702
+ background: var(--ps-accent);
5703
+ color: #FFFFFF;
5704
+ border: 1.5px solid var(--ps-accent);
5705
+ }
5706
+ .ps-cpw-age-gate-btn-primary:hover { background: color-mix(in srgb, var(--ps-accent) 88%, #000); }
5707
+ .ps-cpw-age-gate-btn-secondary {
5708
+ background: transparent;
5709
+ color: var(--ps-text-primary);
5710
+ border: 1.5px solid var(--ps-border-color);
5711
+ }
5712
+ .ps-cpw-age-gate-btn-secondary:hover {
5713
+ background: var(--ps-bg-secondary);
5714
+ border-color: var(--ps-text-muted);
5715
+ }
5716
+ .ps-cpw-age-gate-card-blocked { border-color: rgba(192, 38, 38, 0.35); }
5717
+
5718
+ /* ── Legal notice card on the right column ──
5719
+ Soft neutral card with a small shield icon; matches photo-guide width. */
5720
+ .ps-cpw-legal-notice {
5721
+ background: rgba(33, 84, 239, 0.04);
5722
+ border: 1px solid rgba(33, 84, 239, 0.16);
5723
+ border-radius: clamp(10px, 0.75vw, 16px);
5724
+ padding: clamp(10px, 0.9vw, 18px) clamp(12px, 1vw, 20px);
5725
+ display: flex; flex-direction: column;
5726
+ gap: clamp(5px, 0.45vw, 10px);
5727
+ }
5728
+ .ps-cpw-legal-notice-head {
5729
+ display: flex; align-items: center;
5730
+ gap: clamp(6px, 0.5vw, 10px);
5731
+ font-size: clamp(9px, 0.62vw, 11px);
5732
+ font-weight: 700;
5733
+ letter-spacing: 0.14em;
5734
+ text-transform: uppercase;
5735
+ color: var(--ps-accent);
5736
+ }
5737
+ .ps-cpw-legal-notice-head svg {
5738
+ width: clamp(12px, 0.85vw, 15px);
5739
+ height: clamp(12px, 0.85vw, 15px);
5740
+ }
5741
+ .ps-cpw-legal-notice-body {
5742
+ margin: 0;
5743
+ font-size: clamp(10px, 0.7vw, 12.5px);
5744
+ line-height: 1.5;
5745
+ color: var(--ps-text-secondary);
5746
+ }
5747
+
5373
5748
  .ps-cpw-hint {
5374
5749
  font-size: clamp(10px, 0.72vw, 13px);
5375
5750
  line-height: 1.6;
@@ -7287,6 +7662,102 @@ const SKELETON_CONNECTIONS = [
7287
7662
  ["rightHip", "rightKnee"],
7288
7663
  ["rightKnee", "rightAnkle"]
7289
7664
  ];
7665
+ function FaceOverlay({
7666
+ landmarks,
7667
+ imgWidth,
7668
+ imgHeight
7669
+ }) {
7670
+ const W = imgWidth;
7671
+ const H = imgHeight;
7672
+ const pts = [
7673
+ { key: "forehead", p: landmarks.forehead },
7674
+ { key: "chin", p: landmarks.chin },
7675
+ { key: "noseTip", p: landmarks.noseTip },
7676
+ { key: "noseBridge", p: landmarks.noseBridge },
7677
+ { key: "leftInnerEye", p: landmarks.leftInnerEye },
7678
+ { key: "rightInnerEye", p: landmarks.rightInnerEye },
7679
+ { key: "leftOuterEye", p: landmarks.leftOuterEye },
7680
+ { key: "rightOuterEye", p: landmarks.rightOuterEye },
7681
+ { key: "leftTragus", p: landmarks.leftTragus },
7682
+ { key: "rightTragus", p: landmarks.rightTragus },
7683
+ { key: "leftMouth", p: landmarks.leftMouth },
7684
+ { key: "rightMouth", p: landmarks.rightMouth }
7685
+ ];
7686
+ const connections = [
7687
+ // Face axis
7688
+ [landmarks.forehead, landmarks.noseBridge],
7689
+ [landmarks.noseBridge, landmarks.noseTip],
7690
+ [landmarks.noseTip, landmarks.chin],
7691
+ // Horizontal eye line
7692
+ [landmarks.leftOuterEye, landmarks.leftInnerEye],
7693
+ [landmarks.leftInnerEye, landmarks.rightInnerEye],
7694
+ [landmarks.rightInnerEye, landmarks.rightOuterEye],
7695
+ // Ears
7696
+ [landmarks.leftTragus, landmarks.leftOuterEye],
7697
+ [landmarks.rightOuterEye, landmarks.rightTragus],
7698
+ // Mouth line
7699
+ [landmarks.leftMouth, landmarks.rightMouth]
7700
+ ];
7701
+ return /* @__PURE__ */ jsxs("svg", { className: "ps-tryon-pose-overlay", viewBox: `0 0 ${W} ${H}`, preserveAspectRatio: "xMidYMid meet", children: [
7702
+ connections.map(([a, b], i) => /* @__PURE__ */ jsx(
7703
+ "line",
7704
+ {
7705
+ x1: a.x * W,
7706
+ y1: a.y * H,
7707
+ x2: b.x * W,
7708
+ y2: b.y * H,
7709
+ stroke: "rgba(100,210,255,0.55)",
7710
+ strokeWidth: "3",
7711
+ strokeLinecap: "round",
7712
+ opacity: "0",
7713
+ style: { animation: `ps-pose-fade 0.4s ease ${i * 0.05}s forwards` }
7714
+ },
7715
+ `fl-${i}`
7716
+ )),
7717
+ [landmarks.leftIrisCenter, landmarks.rightIrisCenter].map((c, i) => {
7718
+ const ring = i === 0 ? landmarks.leftIrisRing : landmarks.rightIrisRing;
7719
+ const rx = ring?.length ? Math.abs((ring[0]?.x ?? c.x) - (ring[2]?.x ?? c.x)) * W / 2 : 6;
7720
+ return /* @__PURE__ */ jsx(
7721
+ "circle",
7722
+ {
7723
+ cx: c.x * W,
7724
+ cy: c.y * H,
7725
+ r: Math.max(6, rx),
7726
+ fill: "none",
7727
+ stroke: "rgba(255,230,120,0.95)",
7728
+ strokeWidth: "2.5",
7729
+ opacity: "0",
7730
+ style: { animation: `ps-pose-fade 0.3s ease ${0.3 + i * 0.1}s forwards` }
7731
+ },
7732
+ `iris-${i}`
7733
+ );
7734
+ }),
7735
+ pts.map(({ key, p }, i) => /* @__PURE__ */ jsxs("g", { children: [
7736
+ /* @__PURE__ */ jsx(
7737
+ "circle",
7738
+ {
7739
+ cx: p.x * W,
7740
+ cy: p.y * H,
7741
+ r: "11",
7742
+ fill: "rgba(100,210,255,0.22)",
7743
+ opacity: "0",
7744
+ style: { animation: `ps-pose-fade 0.3s ease ${i * 0.04}s forwards` }
7745
+ }
7746
+ ),
7747
+ /* @__PURE__ */ jsx(
7748
+ "circle",
7749
+ {
7750
+ cx: p.x * W,
7751
+ cy: p.y * H,
7752
+ r: "6",
7753
+ fill: "rgba(100,210,255,0.95)",
7754
+ opacity: "0",
7755
+ 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` }
7756
+ }
7757
+ )
7758
+ ] }, key))
7759
+ ] });
7760
+ }
7290
7761
  function SkeletonOverlay({ landmarks, imgWidth, imgHeight }) {
7291
7762
  const W = imgWidth;
7292
7763
  const H = imgHeight;
@@ -8151,6 +8622,8 @@ function SizeResultView({
8151
8622
  handleTryOnSubmit,
8152
8623
  tryOnProcessing,
8153
8624
  bodyLandmarks,
8625
+ faceLandmarks = null,
8626
+ measurementType = "body",
8154
8627
  estimationDone = false,
8155
8628
  activeSection,
8156
8629
  setActiveSection,
@@ -8437,28 +8910,33 @@ function SizeResultView({
8437
8910
  onLoad: handleImgLoad
8438
8911
  }
8439
8912
  ),
8440
- bodyLandmarks && /* @__PURE__ */ jsx(SkeletonOverlay, { landmarks: bodyLandmarks, imgWidth: imgDims.w, imgHeight: imgDims.h })
8913
+ measurementType === "face" || measurementType === "head" ? faceLandmarks && /* @__PURE__ */ jsx(FaceOverlay, { landmarks: faceLandmarks, imgWidth: imgDims.w, imgHeight: imgDims.h }) : bodyLandmarks && /* @__PURE__ */ jsx(SkeletonOverlay, { landmarks: bodyLandmarks, imgWidth: imgDims.w, imgHeight: imgDims.h })
8441
8914
  ] }),
8442
- /* @__PURE__ */ jsxs("div", { className: "ps-tryon-sr-right-col ps-tryon-snap-steps", children: [
8443
- /* @__PURE__ */ jsxs("div", { className: `ps-tryon-snap-step${bodyLandmarks ? " ps-done" : " ps-active"}`, children: [
8444
- /* @__PURE__ */ jsx("div", { className: "ps-tryon-snap-step-icon", children: bodyLandmarks ? /* @__PURE__ */ jsx("span", { className: "ps-tryon-snap-check", children: "✓" }) : /* @__PURE__ */ jsx("div", { className: "ps-tryon-size-loading-spinner", style: { width: "1vw", height: "1vw", borderWidth: "1.5px" } }) }),
8445
- /* @__PURE__ */ jsx("span", { children: t("Detecting body pose") })
8446
- ] }),
8447
- !sizingDone && /* @__PURE__ */ jsxs(Fragment, { children: [
8448
- /* @__PURE__ */ jsxs("div", { className: `ps-tryon-snap-step${analyzingDone ? " ps-done" : bodyLandmarks ? " ps-active" : ""}`, children: [
8449
- /* @__PURE__ */ jsx("div", { className: "ps-tryon-snap-step-icon", children: !bodyLandmarks ? /* @__PURE__ */ jsx("span", { className: "ps-tryon-snap-num", children: "2" }) : !analyzingDone ? /* @__PURE__ */ jsx("div", { className: "ps-tryon-size-loading-spinner", style: { width: "1vw", height: "1vw", borderWidth: "1.5px" } }) : /* @__PURE__ */ jsx("span", { className: "ps-tryon-snap-check", children: "✓" }) }),
8450
- /* @__PURE__ */ jsx("span", { children: t("Analyzing your size") })
8915
+ (() => {
8916
+ const isFaceCategory = measurementType === "face" || measurementType === "head";
8917
+ const detectionDone = isFaceCategory ? !!faceLandmarks : !!bodyLandmarks;
8918
+ const detectLabel = isFaceCategory ? measurementType === "head" ? t("Detecting head") : t("Detecting face") : t("Detecting body pose");
8919
+ return /* @__PURE__ */ jsxs("div", { className: "ps-tryon-sr-right-col ps-tryon-snap-steps", children: [
8920
+ /* @__PURE__ */ jsxs("div", { className: `ps-tryon-snap-step${detectionDone ? " ps-done" : " ps-active"}`, children: [
8921
+ /* @__PURE__ */ jsx("div", { className: "ps-tryon-snap-step-icon", children: detectionDone ? /* @__PURE__ */ jsx("span", { className: "ps-tryon-snap-check", children: "✓" }) : /* @__PURE__ */ jsx("div", { className: "ps-tryon-size-loading-spinner", style: { width: "1vw", height: "1vw", borderWidth: "1.5px" } }) }),
8922
+ /* @__PURE__ */ jsx("span", { children: detectLabel })
8451
8923
  ] }),
8452
- /* @__PURE__ */ jsxs("div", { className: `ps-tryon-snap-step${analyzingDone ? " ps-active" : ""}`, children: [
8453
- /* @__PURE__ */ jsx("div", { className: "ps-tryon-snap-step-icon", children: !analyzingDone ? /* @__PURE__ */ jsx("span", { className: "ps-tryon-snap-num", children: "3" }) : /* @__PURE__ */ jsx("div", { className: "ps-tryon-size-loading-spinner", style: { width: "1vw", height: "1vw", borderWidth: "1.5px" } }) }),
8454
- /* @__PURE__ */ jsx("span", { children: t("Finding best fit for you") })
8924
+ !sizingDone && /* @__PURE__ */ jsxs(Fragment, { children: [
8925
+ /* @__PURE__ */ jsxs("div", { className: `ps-tryon-snap-step${analyzingDone ? " ps-done" : detectionDone ? " ps-active" : ""}`, children: [
8926
+ /* @__PURE__ */ jsx("div", { className: "ps-tryon-snap-step-icon", children: !detectionDone ? /* @__PURE__ */ jsx("span", { className: "ps-tryon-snap-num", children: "2" }) : !analyzingDone ? /* @__PURE__ */ jsx("div", { className: "ps-tryon-size-loading-spinner", style: { width: "1vw", height: "1vw", borderWidth: "1.5px" } }) : /* @__PURE__ */ jsx("span", { className: "ps-tryon-snap-check", children: "✓" }) }),
8927
+ /* @__PURE__ */ jsx("span", { children: t("Analyzing your size") })
8928
+ ] }),
8929
+ /* @__PURE__ */ jsxs("div", { className: `ps-tryon-snap-step${analyzingDone ? " ps-active" : ""}`, children: [
8930
+ /* @__PURE__ */ jsx("div", { className: "ps-tryon-snap-step-icon", children: !analyzingDone ? /* @__PURE__ */ jsx("span", { className: "ps-tryon-snap-num", children: "3" }) : /* @__PURE__ */ jsx("div", { className: "ps-tryon-size-loading-spinner", style: { width: "1vw", height: "1vw", borderWidth: "1.5px" } }) }),
8931
+ /* @__PURE__ */ jsx("span", { children: t("Finding best fit for you") })
8932
+ ] })
8933
+ ] }),
8934
+ tryOnProcessing && /* @__PURE__ */ jsxs("div", { className: `ps-tryon-snap-step${tryOnDone ? " ps-done" : " ps-active"}`, children: [
8935
+ /* @__PURE__ */ jsx("div", { className: "ps-tryon-snap-step-icon", children: tryOnDone ? /* @__PURE__ */ jsx("span", { className: "ps-tryon-snap-check", children: "✓" }) : /* @__PURE__ */ jsx("div", { className: "ps-tryon-size-loading-spinner", style: { width: "1vw", height: "1vw", borderWidth: "1.5px" } }) }),
8936
+ /* @__PURE__ */ jsx("span", { children: t("Generating virtual try-on") })
8455
8937
  ] })
8456
- ] }),
8457
- tryOnProcessing && /* @__PURE__ */ jsxs("div", { className: `ps-tryon-snap-step${tryOnDone ? " ps-done" : " ps-active"}`, children: [
8458
- /* @__PURE__ */ jsx("div", { className: "ps-tryon-snap-step-icon", children: tryOnDone ? /* @__PURE__ */ jsx("span", { className: "ps-tryon-snap-check", children: "✓" }) : /* @__PURE__ */ jsx("div", { className: "ps-tryon-size-loading-spinner", style: { width: "1vw", height: "1vw", borderWidth: "1.5px" } }) }),
8459
- /* @__PURE__ */ jsx("span", { children: t("Generating virtual try-on") })
8460
- ] })
8461
- ] })
8938
+ ] });
8939
+ })()
8462
8940
  ] }),
8463
8941
  (allDone || sizingResult && !isSnapProcessing) && /* @__PURE__ */ jsxs(Fragment, { children: [
8464
8942
  isMultiSection ? activeSection ? (
@@ -9257,9 +9735,18 @@ function CreateProfileWizard({ onSave, onCancel, onPhotoPreview, onEstimate, t }
9257
9735
  const photoInputRef = useRef(null);
9258
9736
  const nameInputRef = useRef(null);
9259
9737
  const [nameShaking, setNameShaking] = useState(false);
9738
+ const [ageConfirmed, setAgeConfirmed] = useState(null);
9739
+ const openFilePicker = () => {
9740
+ if (ageConfirmed !== true) return;
9741
+ photoInputRef.current?.click();
9742
+ };
9260
9743
  const handlePhotoSelect = async (e) => {
9261
9744
  const file = e.target.files?.[0];
9262
9745
  if (!file) return;
9746
+ if (ageConfirmed !== true) {
9747
+ setError(t("Please confirm that the person in the photo is 18 or older before uploading."));
9748
+ return;
9749
+ }
9263
9750
  if (!file.type.startsWith("image/")) {
9264
9751
  setError(t("Please upload an image file"));
9265
9752
  return;
@@ -9840,18 +10327,71 @@ function CreateProfileWizard({ onSave, onCancel, onPhotoPreview, onEstimate, t }
9840
10327
  /* @__PURE__ */ jsx("div", { className: "ps-cpw-image-left", children: photoBase64 ? /* @__PURE__ */ jsxs("div", { className: "ps-cpw-photo-preview-frame", children: [
9841
10328
  /* @__PURE__ */ jsx("img", { src: photoBase64, alt: t("Profile photo"), className: "ps-cpw-photo-preview-img" }),
9842
10329
  /* @__PURE__ */ jsx("button", { type: "button", className: "ps-cpw-photo-remove", onClick: handleRemovePhoto, "aria-label": t("Remove photo"), children: "×" }),
9843
- /* @__PURE__ */ jsx("button", { type: "button", className: "ps-cpw-photo-retake-pill", onClick: () => photoInputRef.current?.click(), children: t("Retake") })
9844
- ] }) : /* @__PURE__ */ jsxs("button", { type: "button", className: "ps-cpw-dropzone", onClick: () => photoInputRef.current?.click(), disabled: photoUploading, children: [
9845
- /* @__PURE__ */ jsx("img", { src: photoUploadIllustrationImg, alt: "", "aria-hidden": "true", className: "ps-cpw-dropzone-silhouette" }),
9846
- /* @__PURE__ */ jsxs("div", { className: "ps-cpw-dropzone-content", children: [
9847
- /* @__PURE__ */ 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: [
9848
- /* @__PURE__ */ jsx("path", { d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" }),
9849
- /* @__PURE__ */ jsx("polyline", { points: "17 8 12 3 7 8" }),
9850
- /* @__PURE__ */ jsx("line", { x1: "12", y1: "3", x2: "12", y2: "15" })
9851
- ] }),
9852
- /* @__PURE__ */ jsx("span", { className: "ps-cpw-dropzone-title", children: photoUploading ? t("Processing...") : t("Drop a photo or click to upload") }),
9853
- /* @__PURE__ */ jsx("span", { className: "ps-cpw-dropzone-hint", children: t("JPEG · PNG · WebP · up to 10MB") })
9854
- ] })
10330
+ /* @__PURE__ */ jsx("button", { type: "button", className: "ps-cpw-photo-retake-pill", onClick: openFilePicker, children: t("Retake") })
10331
+ ] }) : /* @__PURE__ */ jsxs("div", { className: "ps-cpw-dropzone-wrap", children: [
10332
+ /* @__PURE__ */ jsxs(
10333
+ "button",
10334
+ {
10335
+ type: "button",
10336
+ className: `ps-cpw-dropzone${ageConfirmed !== true ? " ps-cpw-dropzone-blurred" : ""}`,
10337
+ onClick: openFilePicker,
10338
+ disabled: photoUploading || ageConfirmed !== true,
10339
+ "aria-hidden": ageConfirmed !== true,
10340
+ tabIndex: ageConfirmed !== true ? -1 : 0,
10341
+ children: [
10342
+ /* @__PURE__ */ jsx("img", { src: photoUploadIllustrationImg, alt: "", "aria-hidden": "true", className: "ps-cpw-dropzone-silhouette" }),
10343
+ /* @__PURE__ */ jsxs("div", { className: "ps-cpw-dropzone-content", children: [
10344
+ /* @__PURE__ */ 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: [
10345
+ /* @__PURE__ */ jsx("path", { d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" }),
10346
+ /* @__PURE__ */ jsx("polyline", { points: "17 8 12 3 7 8" }),
10347
+ /* @__PURE__ */ jsx("line", { x1: "12", y1: "3", x2: "12", y2: "15" })
10348
+ ] }),
10349
+ /* @__PURE__ */ jsx("span", { className: "ps-cpw-dropzone-title", children: photoUploading ? t("Processing...") : t("Drop a photo or click to upload") }),
10350
+ /* @__PURE__ */ jsx("span", { className: "ps-cpw-dropzone-hint", children: t("JPEG · PNG · WebP · up to 10MB") })
10351
+ ] })
10352
+ ]
10353
+ }
10354
+ ),
10355
+ ageConfirmed === null && /* @__PURE__ */ jsx("div", { className: "ps-cpw-age-gate", role: "dialog", "aria-labelledby": "ps-cpw-age-gate-q", children: /* @__PURE__ */ jsxs("div", { className: "ps-cpw-age-gate-card", children: [
10356
+ /* @__PURE__ */ jsx("div", { className: "ps-cpw-age-gate-eyebrow", children: t("AGE VERIFICATION") }),
10357
+ /* @__PURE__ */ jsx("div", { id: "ps-cpw-age-gate-q", className: "ps-cpw-age-gate-question", children: t("Is the person in this photo 18 years or older?") }),
10358
+ /* @__PURE__ */ jsxs("div", { className: "ps-cpw-age-gate-actions", children: [
10359
+ /* @__PURE__ */ jsx(
10360
+ "button",
10361
+ {
10362
+ type: "button",
10363
+ className: "ps-cpw-age-gate-btn ps-cpw-age-gate-btn-primary",
10364
+ onClick: () => {
10365
+ setAgeConfirmed(true);
10366
+ setError("");
10367
+ },
10368
+ children: t("Yes, 18 or older")
10369
+ }
10370
+ ),
10371
+ /* @__PURE__ */ jsx(
10372
+ "button",
10373
+ {
10374
+ type: "button",
10375
+ className: "ps-cpw-age-gate-btn ps-cpw-age-gate-btn-secondary",
10376
+ onClick: () => setAgeConfirmed(false),
10377
+ children: t("No, under 18")
10378
+ }
10379
+ )
10380
+ ] })
10381
+ ] }) }),
10382
+ ageConfirmed === false && /* @__PURE__ */ jsx("div", { className: "ps-cpw-age-gate", role: "alert", children: /* @__PURE__ */ jsxs("div", { className: "ps-cpw-age-gate-card ps-cpw-age-gate-card-blocked", children: [
10383
+ /* @__PURE__ */ jsx("div", { className: "ps-cpw-age-gate-eyebrow ps-cpw-age-gate-eyebrow-blocked", children: t("UPLOAD NOT ALLOWED") }),
10384
+ /* @__PURE__ */ jsx("div", { className: "ps-cpw-age-gate-question", children: t("For your safety, we cannot process photos of people under 18.") }),
10385
+ /* @__PURE__ */ jsx(
10386
+ "button",
10387
+ {
10388
+ type: "button",
10389
+ className: "ps-cpw-age-gate-btn ps-cpw-age-gate-btn-secondary",
10390
+ onClick: () => setAgeConfirmed(null),
10391
+ children: t("Go back")
10392
+ }
10393
+ )
10394
+ ] }) })
9855
10395
  ] }) }),
9856
10396
  /* @__PURE__ */ jsxs("div", { className: "ps-cpw-image-right", children: [
9857
10397
  /* @__PURE__ */ jsx("div", { className: "ps-bp-inline-fields ps-cpw-inline-fields", children: /* @__PURE__ */ jsxs("div", { className: "ps-bp-inline-row", children: [
@@ -9882,6 +10422,13 @@ function CreateProfileWizard({ onSave, onCancel, onPhotoPreview, onEstimate, t }
9882
10422
  ] })
9883
10423
  ] })
9884
10424
  ] }),
10425
+ /* @__PURE__ */ jsxs("div", { className: "ps-cpw-legal-notice", children: [
10426
+ /* @__PURE__ */ jsxs("div", { className: "ps-cpw-legal-notice-head", children: [
10427
+ /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { d: "M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z" }) }),
10428
+ /* @__PURE__ */ jsx("span", { children: t("LEGAL NOTICE") })
10429
+ ] }),
10430
+ /* @__PURE__ */ jsx("p", { className: "ps-cpw-legal-notice-body", children: t("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.") })
10431
+ ] }),
9885
10432
  error && /* @__PURE__ */ jsx("div", { className: "ps-cpw-error", children: error })
9886
10433
  ] })
9887
10434
  ] }, "image-photo"),
@@ -10725,6 +11272,8 @@ function PhotoStepMobile({
10725
11272
  const isCloseUp = photoVariant === "close-up";
10726
11273
  const fileRef = useRef(null);
10727
11274
  const hasPhoto = !!photoPreview;
11275
+ const [ageConfirmed, setAgeConfirmed] = useState(null);
11276
+ const gated = !hasPhoto && ageConfirmed !== true;
10728
11277
  return /* @__PURE__ */ jsxs("div", { className: "ps-pm-root", children: [
10729
11278
  /* @__PURE__ */ jsxs("div", { className: "ps-pm-header", children: [
10730
11279
  /* @__PURE__ */ jsx("h2", { className: "ps-pm-title", children: isCloseUp ? t("Upload a face photo or selfie") : t("Review your photo") }),
@@ -10752,19 +11301,46 @@ function PhotoStepMobile({
10752
11301
  children: /* @__PURE__ */ jsx(CloseIconSm, {})
10753
11302
  }
10754
11303
  )
10755
- ] }) : /* @__PURE__ */ jsxs(
10756
- "button",
10757
- {
10758
- type: "button",
10759
- className: "ps-pm-preview-empty",
10760
- onClick: () => fileRef.current?.click(),
10761
- children: [
10762
- /* @__PURE__ */ jsx(UploadIconLg, {}),
10763
- /* @__PURE__ */ jsx("span", { className: "ps-pm-preview-empty-title", children: t("Tap to upload") }),
10764
- /* @__PURE__ */ jsx("span", { className: "ps-pm-preview-empty-hint", children: t("JPEG, PNG up to 10MB") })
10765
- ]
10766
- }
10767
- ) }),
11304
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
11305
+ /* @__PURE__ */ jsxs(
11306
+ "button",
11307
+ {
11308
+ type: "button",
11309
+ className: `ps-pm-preview-empty${gated ? " ps-pm-preview-blurred" : ""}`,
11310
+ onClick: () => {
11311
+ if (!gated) fileRef.current?.click();
11312
+ },
11313
+ disabled: gated,
11314
+ "aria-hidden": gated,
11315
+ tabIndex: gated ? -1 : 0,
11316
+ children: [
11317
+ /* @__PURE__ */ jsx(UploadIconLg, {}),
11318
+ /* @__PURE__ */ jsx("span", { className: "ps-pm-preview-empty-title", children: t("Tap to upload") }),
11319
+ /* @__PURE__ */ jsx("span", { className: "ps-pm-preview-empty-hint", children: t("JPEG, PNG up to 10MB") })
11320
+ ]
11321
+ }
11322
+ ),
11323
+ ageConfirmed === null && /* @__PURE__ */ jsx("div", { className: "ps-pm-age-gate", role: "dialog", children: /* @__PURE__ */ jsxs("div", { className: "ps-pm-age-gate-card", children: [
11324
+ /* @__PURE__ */ jsx("div", { className: "ps-pm-age-gate-eyebrow", children: t("AGE VERIFICATION") }),
11325
+ /* @__PURE__ */ jsx("div", { className: "ps-pm-age-gate-question", children: t("Is the person in this photo 18 years or older?") }),
11326
+ /* @__PURE__ */ jsxs("div", { className: "ps-pm-age-gate-actions", children: [
11327
+ /* @__PURE__ */ jsx("button", { type: "button", className: "ps-pm-age-gate-btn ps-pm-age-gate-btn-primary", onClick: () => setAgeConfirmed(true), children: t("Yes, 18 or older") }),
11328
+ /* @__PURE__ */ jsx("button", { type: "button", className: "ps-pm-age-gate-btn ps-pm-age-gate-btn-secondary", onClick: () => setAgeConfirmed(false), children: t("No, under 18") })
11329
+ ] })
11330
+ ] }) }),
11331
+ ageConfirmed === false && /* @__PURE__ */ jsx("div", { className: "ps-pm-age-gate", role: "alert", children: /* @__PURE__ */ jsxs("div", { className: "ps-pm-age-gate-card", children: [
11332
+ /* @__PURE__ */ jsx("div", { className: "ps-pm-age-gate-eyebrow ps-pm-age-gate-eyebrow-blocked", children: t("UPLOAD NOT ALLOWED") }),
11333
+ /* @__PURE__ */ jsx("div", { className: "ps-pm-age-gate-question", children: t("For your safety, we cannot process photos of people under 18.") }),
11334
+ /* @__PURE__ */ jsx("button", { type: "button", className: "ps-pm-age-gate-btn ps-pm-age-gate-btn-secondary", onClick: () => setAgeConfirmed(null), children: t("Go back") })
11335
+ ] }) })
11336
+ ] }) }),
11337
+ /* @__PURE__ */ jsxs("div", { className: "ps-pm-legal-notice", children: [
11338
+ /* @__PURE__ */ jsxs("div", { className: "ps-pm-legal-notice-head", children: [
11339
+ /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { d: "M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z" }) }),
11340
+ /* @__PURE__ */ jsx("span", { children: t("LEGAL NOTICE") })
11341
+ ] }),
11342
+ /* @__PURE__ */ jsx("p", { className: "ps-pm-legal-notice-body", children: t("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.") })
11343
+ ] }),
10768
11344
  /* @__PURE__ */ jsxs("div", { className: "ps-pm-checklist", children: [
10769
11345
  /* @__PURE__ */ jsx("div", { className: "ps-pm-checklist-icon", children: /* @__PURE__ */ jsx(InfoIcon, {}) }),
10770
11346
  /* @__PURE__ */ jsxs("div", { className: "ps-pm-checklist-body", children: [
@@ -12118,7 +12694,6 @@ function HeadSizeView(props) {
12118
12694
  title: "Headwear Measurements",
12119
12695
  fields,
12120
12696
  photoVariant: "close-up",
12121
- disablePhotoUpload: true,
12122
12697
  ...rest
12123
12698
  }
12124
12699
  );
@@ -12178,7 +12753,6 @@ function FaceSizeView(props) {
12178
12753
  fields,
12179
12754
  unitOptions: EYEWEAR_UNIT_OPTIONS,
12180
12755
  photoVariant: "close-up",
12181
- disablePhotoUpload: true,
12182
12756
  ...rest
12183
12757
  }
12184
12758
  );
@@ -12288,6 +12862,7 @@ function PrimeStyleTryonInner({
12288
12862
  const bodyRef = useRef(null);
12289
12863
  const modelPoseRef = useRef(null);
12290
12864
  const [bodyLandmarks, setBodyLandmarks] = useState(null);
12865
+ const [faceLandmarks, setFaceLandmarks] = useState(null);
12291
12866
  const selectedFileRef = useRef(null);
12292
12867
  useEffect(() => {
12293
12868
  try {
@@ -12866,6 +13441,55 @@ function PrimeStyleTryonInner({
12866
13441
  setSizingLoading(true);
12867
13442
  setEstimationDone(false);
12868
13443
  setView("size-result");
13444
+ const measurementType = detectMeasurementType(productTitle);
13445
+ if (measurementType === "face" || measurementType === "head") {
13446
+ setFaceLandmarks(null);
13447
+ try {
13448
+ const faceResult = await detectFaceMeasurements(objUrl);
13449
+ if (faceResult) setFaceLandmarks(faceResult.landmarks);
13450
+ const facePayload = {
13451
+ product: { title: productTitle },
13452
+ sizeGuide: sizeGuide ?? { found: false },
13453
+ sizingUnit: measurementType === "head" ? "cm" : "mm",
13454
+ category: measurementType,
13455
+ bodyImage: data.photoBase64
13456
+ };
13457
+ if (faceResult) {
13458
+ facePayload.faceMeasurementsMm = faceResult.measurementsMm;
13459
+ facePayload.faceLandmarks = faceResult.landmarks;
13460
+ facePayload.irisConfidence = faceResult.irisConfidence;
13461
+ }
13462
+ const recRes = await fetch(`${baseUrl}/api/v1/sizing/face-recommend`, {
13463
+ method: "POST",
13464
+ headers: { "Content-Type": "application/json", Authorization: `Bearer ${key}` },
13465
+ body: JSON.stringify(facePayload)
13466
+ });
13467
+ if (recRes.ok) {
13468
+ const recData = await recRes.json();
13469
+ setSizingResult(recData);
13470
+ onComplete?.(recData);
13471
+ persistResultToProfile(
13472
+ {
13473
+ gender: data.gender,
13474
+ height: data.height,
13475
+ weight: data.weight,
13476
+ heightUnit: data.heightUnit,
13477
+ weightUnit: data.weightUnit,
13478
+ age: data.age,
13479
+ bodyImage: data.photoBase64
13480
+ },
13481
+ recData
13482
+ );
13483
+ } else {
13484
+ setEstimationDone(true);
13485
+ }
13486
+ } catch (err) {
13487
+ console.error("[ps-sdk] face-recommend failed:", err);
13488
+ setEstimationDone(true);
13489
+ }
13490
+ setSizingLoading(false);
13491
+ return;
13492
+ }
12869
13493
  modelPoseRef.current = null;
12870
13494
  setBodyLandmarks(null);
12871
13495
  detectMeasurementLines(objUrl).then((lines) => {
@@ -13476,6 +14100,8 @@ function PrimeStyleTryonInner({
13476
14100
  handleTryOnSubmit,
13477
14101
  tryOnProcessing,
13478
14102
  bodyLandmarks,
14103
+ faceLandmarks,
14104
+ measurementType: detectMeasurementType(productTitle),
13479
14105
  activeSection,
13480
14106
  setActiveSection,
13481
14107
  onResetTryOn: () => {