@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.
@@ -9479,11 +9479,11 @@ const LEFT_ANKLE = 27;
9479
9479
  const RIGHT_ANKLE = 28;
9480
9480
  const NOSE = 0;
9481
9481
  let poseLandmarker = null;
9482
- let loadingPromise = null;
9482
+ let loadingPromise$1 = null;
9483
9483
  async function loadMediaPipe() {
9484
9484
  if (poseLandmarker) return;
9485
- if (loadingPromise) return loadingPromise;
9486
- loadingPromise = (async () => {
9485
+ if (loadingPromise$1) return loadingPromise$1;
9486
+ loadingPromise$1 = (async () => {
9487
9487
  const vision = await import(
9488
9488
  /* webpackIgnore: true */
9489
9489
  // @ts-ignore dynamic CDN import
@@ -9502,12 +9502,12 @@ async function loadMediaPipe() {
9502
9502
  numPoses: 1
9503
9503
  });
9504
9504
  })();
9505
- return loadingPromise;
9505
+ return loadingPromise$1;
9506
9506
  }
9507
9507
  async function detectMeasurementLines(imageSrc) {
9508
9508
  try {
9509
9509
  await loadMediaPipe();
9510
- const img = await loadImage(imageSrc);
9510
+ const img = await loadImage$1(imageSrc);
9511
9511
  const result = poseLandmarker.detect(img);
9512
9512
  if (!result?.landmarks?.length || result.landmarks[0].length < 25) {
9513
9513
  return null;
@@ -9542,7 +9542,7 @@ async function detectMeasurementLines(imageSrc) {
9542
9542
  return null;
9543
9543
  }
9544
9544
  }
9545
- function loadImage(src) {
9545
+ function loadImage$1(src) {
9546
9546
  return new Promise((resolve, reject) => {
9547
9547
  const img = new Image();
9548
9548
  img.crossOrigin = "anonymous";
@@ -9561,7 +9561,7 @@ async function detectBodyLandmarks(imageSrc) {
9561
9561
  await loadMediaPipe();
9562
9562
  let img;
9563
9563
  if (typeof imageSrc === "string") {
9564
- img = await loadImage(imageSrc);
9564
+ img = await loadImage$1(imageSrc);
9565
9565
  } else {
9566
9566
  img = imageSrc;
9567
9567
  }
@@ -9590,6 +9590,165 @@ async function detectBodyLandmarks(imageSrc) {
9590
9590
  return null;
9591
9591
  }
9592
9592
  }
9593
+ const IDX = {
9594
+ noseTip: 1,
9595
+ noseBridge: 168,
9596
+ leftInnerEye: 133,
9597
+ rightInnerEye: 362,
9598
+ leftOuterEye: 33,
9599
+ rightOuterEye: 263,
9600
+ // Iris landmarks (478-point model with iris refinement)
9601
+ leftIrisCenter: 468,
9602
+ leftIrisRing: [469, 470, 471, 472],
9603
+ rightIrisCenter: 473,
9604
+ rightIrisRing: [474, 475, 476, 477],
9605
+ // Tragus (ear attach point) — best approximations in the face mesh
9606
+ leftTragus: 234,
9607
+ rightTragus: 454,
9608
+ forehead: 10,
9609
+ chin: 152,
9610
+ leftMouth: 61,
9611
+ rightMouth: 291
9612
+ };
9613
+ const IRIS_DIAMETER_MM = 11.7;
9614
+ let faceLandmarker = null;
9615
+ let loadingPromise = null;
9616
+ async function loadFaceLandmarker() {
9617
+ if (faceLandmarker) return;
9618
+ if (loadingPromise) return loadingPromise;
9619
+ loadingPromise = (async () => {
9620
+ const vision = await import(
9621
+ /* webpackIgnore: true */
9622
+ // @ts-ignore dynamic CDN import
9623
+ "https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@0.10.33/vision_bundle.mjs"
9624
+ );
9625
+ const { FilesetResolver, FaceLandmarker } = vision;
9626
+ const filesetResolver = await FilesetResolver.forVisionTasks(
9627
+ "https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@0.10.33/wasm"
9628
+ );
9629
+ faceLandmarker = await FaceLandmarker.createFromOptions(filesetResolver, {
9630
+ baseOptions: {
9631
+ modelAssetPath: "https://storage.googleapis.com/mediapipe-models/face_landmarker/face_landmarker/float16/latest/face_landmarker.task",
9632
+ delegate: "GPU"
9633
+ },
9634
+ runningMode: "IMAGE",
9635
+ numFaces: 1,
9636
+ outputFaceBlendshapes: false,
9637
+ outputFacialTransformationMatrixes: false
9638
+ });
9639
+ })();
9640
+ return loadingPromise;
9641
+ }
9642
+ function loadImage(src) {
9643
+ return new Promise((resolve, reject) => {
9644
+ const img = new Image();
9645
+ img.crossOrigin = "anonymous";
9646
+ img.onload = () => resolve(img);
9647
+ img.onerror = () => {
9648
+ const img2 = new Image();
9649
+ img2.onload = () => resolve(img2);
9650
+ img2.onerror = () => reject(new Error("Failed to load image"));
9651
+ img2.src = src;
9652
+ };
9653
+ img.src = src;
9654
+ });
9655
+ }
9656
+ function irisDiameterPx(ring, imageWidth, imageHeight) {
9657
+ if (ring.length < 4) return 0;
9658
+ const toPx = (p4) => ({ x: p4.x * imageWidth, y: p4.y * imageHeight });
9659
+ const [p0, p1, p2, p3] = ring.map(toPx);
9660
+ const d1 = Math.hypot(p0.x - p2.x, p0.y - p2.y);
9661
+ const d2 = Math.hypot(p1.x - p3.x, p1.y - p3.y);
9662
+ return (d1 + d2) / 2;
9663
+ }
9664
+ function extractLandmarks(raw) {
9665
+ if (raw.length < 478) return null;
9666
+ const at = (i) => ({ x: raw[i].x, y: raw[i].y, z: raw[i].z });
9667
+ return {
9668
+ noseTip: at(IDX.noseTip),
9669
+ noseBridge: at(IDX.noseBridge),
9670
+ leftInnerEye: at(IDX.leftInnerEye),
9671
+ rightInnerEye: at(IDX.rightInnerEye),
9672
+ leftOuterEye: at(IDX.leftOuterEye),
9673
+ rightOuterEye: at(IDX.rightOuterEye),
9674
+ leftIrisCenter: at(IDX.leftIrisCenter),
9675
+ rightIrisCenter: at(IDX.rightIrisCenter),
9676
+ leftIrisRing: IDX.leftIrisRing.map(at),
9677
+ rightIrisRing: IDX.rightIrisRing.map(at),
9678
+ leftTragus: at(IDX.leftTragus),
9679
+ rightTragus: at(IDX.rightTragus),
9680
+ forehead: at(IDX.forehead),
9681
+ chin: at(IDX.chin),
9682
+ leftMouth: at(IDX.leftMouth),
9683
+ rightMouth: at(IDX.rightMouth)
9684
+ };
9685
+ }
9686
+ function computeMeasurements(lm, imageWidth, imageHeight) {
9687
+ const leftPx = irisDiameterPx(lm.leftIrisRing, imageWidth, imageHeight);
9688
+ const rightPx = irisDiameterPx(lm.rightIrisRing, imageWidth, imageHeight);
9689
+ const irisPx = (leftPx + rightPx) / 2;
9690
+ let irisConfidence = 1;
9691
+ if (irisPx < 8) irisConfidence = 0.3;
9692
+ else if (Math.abs(leftPx - rightPx) / irisPx > 0.3) irisConfidence = 0.5;
9693
+ else if (Math.abs(leftPx - rightPx) / irisPx > 0.15) irisConfidence = 0.8;
9694
+ const pxToMm = irisPx > 0 ? IRIS_DIAMETER_MM / irisPx : 0;
9695
+ const mmBetween = (a2, b2) => {
9696
+ const pxDist = Math.hypot(
9697
+ (a2.x - b2.x) * imageWidth,
9698
+ (a2.y - b2.y) * imageHeight
9699
+ );
9700
+ return pxDist * pxToMm;
9701
+ };
9702
+ const pd2 = mmBetween(lm.leftIrisCenter, lm.rightIrisCenter);
9703
+ const bridgeWidth = mmBetween(lm.leftInnerEye, lm.rightInnerEye);
9704
+ const faceWidth = mmBetween(lm.leftTragus, lm.rightTragus);
9705
+ const templeLengthLeft = mmBetween(lm.leftTragus, lm.leftOuterEye);
9706
+ const templeLengthRight = mmBetween(lm.rightTragus, lm.rightOuterEye);
9707
+ const templeLength = (templeLengthLeft + templeLengthRight) / 2;
9708
+ const headWidth = faceWidth * 1.07;
9709
+ const zDepthNorm = Math.abs((lm.forehead.z ?? 0) - (lm.chin.z ?? 0));
9710
+ const rawHeadDepthMm = zDepthNorm * imageWidth * pxToMm;
9711
+ const headDepth = Math.max(170, Math.min(210, rawHeadDepthMm || 190));
9712
+ const a = headWidth / 2;
9713
+ const b = headDepth / 2;
9714
+ const headCircumference = Math.PI * Math.sqrt(2 * (a * a + b * b));
9715
+ return {
9716
+ measurements: {
9717
+ irisDiameter: IRIS_DIAMETER_MM,
9718
+ pd: round1(pd2),
9719
+ bridgeWidth: round1(bridgeWidth),
9720
+ faceWidth: round1(faceWidth),
9721
+ templeLengthLeft: round1(templeLengthLeft),
9722
+ templeLengthRight: round1(templeLengthRight),
9723
+ templeLength: round1(templeLength),
9724
+ headWidth: round1(headWidth),
9725
+ headDepth: round1(headDepth),
9726
+ headCircumference: round1(headCircumference)
9727
+ },
9728
+ irisConfidence
9729
+ };
9730
+ }
9731
+ function round1(n2) {
9732
+ return Math.round(n2 * 10) / 10;
9733
+ }
9734
+ async function detectFaceMeasurements(imageSrc) {
9735
+ try {
9736
+ await loadFaceLandmarker();
9737
+ const img = typeof imageSrc === "string" ? await loadImage(imageSrc) : imageSrc;
9738
+ const result = faceLandmarker.detect(img);
9739
+ if (!result?.faceLandmarks?.length) return null;
9740
+ const raw = result.faceLandmarks[0];
9741
+ const landmarks = extractLandmarks(raw);
9742
+ if (!landmarks) return null;
9743
+ const imageWidth = img.naturalWidth || img.width;
9744
+ const imageHeight = img.naturalHeight || img.height;
9745
+ const { measurements, irisConfidence } = computeMeasurements(landmarks, imageWidth, imageHeight);
9746
+ return { landmarks, measurementsMm: measurements, irisConfidence, imageWidth, imageHeight };
9747
+ } catch (err) {
9748
+ console.error("[PS-SDK] Face detection failed:", err);
9749
+ return null;
9750
+ }
9751
+ }
9593
9752
  function parseRange(s) {
9594
9753
  const ns = s.replace(/[^\d.\-–]/g, " ").trim().split(/[\s\-–]+/).filter(Boolean).map(Number).filter((n2) => !isNaN(n2));
9595
9754
  return ns.length ? { min: Math.min(...ns), max: Math.max(...ns) } : { min: 0, max: 0 };
@@ -12933,6 +13092,101 @@ const STYLES$1 = `
12933
13092
  }
12934
13093
  .ps-pm-preview-remove:hover { background: #FFFFFF; }
12935
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
+
12936
13190
  /* Checklist for accuracy card */
12937
13191
  .ps-pm-checklist {
12938
13192
  display: flex; gap: max(12px, 3.1vw);
@@ -14794,6 +15048,127 @@ const STYLES$1 = `
14794
15048
  transition: background 0.18s;
14795
15049
  }
14796
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
+
14797
15172
  .ps-cpw-hint {
14798
15173
  font-size: clamp(10px, 0.72vw, 13px);
14799
15174
  line-height: 1.6;
@@ -16711,6 +17086,102 @@ const SKELETON_CONNECTIONS = [
16711
17086
  ["rightHip", "rightKnee"],
16712
17087
  ["rightKnee", "rightAnkle"]
16713
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
+ }
16714
17185
  function SkeletonOverlay({ landmarks, imgWidth, imgHeight }) {
16715
17186
  const W2 = imgWidth;
16716
17187
  const H2 = imgHeight;
@@ -17575,6 +18046,8 @@ function SizeResultView({
17575
18046
  handleTryOnSubmit,
17576
18047
  tryOnProcessing,
17577
18048
  bodyLandmarks,
18049
+ faceLandmarks = null,
18050
+ measurementType = "body",
17578
18051
  estimationDone = false,
17579
18052
  activeSection,
17580
18053
  setActiveSection,
@@ -17861,28 +18334,33 @@ function SizeResultView({
17861
18334
  onLoad: handleImgLoad
17862
18335
  }
17863
18336
  ),
17864
- 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 })
17865
18338
  ] }),
17866
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-tryon-sr-right-col ps-tryon-snap-steps", children: [
17867
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: `ps-tryon-snap-step${bodyLandmarks ? " ps-done" : " ps-active"}`, children: [
17868
- /* @__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" } }) }),
17869
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: t2("Detecting body pose") })
17870
- ] }),
17871
- !sizingDone && /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
17872
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: `ps-tryon-snap-step${analyzingDone ? " ps-done" : bodyLandmarks ? " ps-active" : ""}`, children: [
17873
- /* @__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: "✓" }) }),
17874
- /* @__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 })
17875
18347
  ] }),
17876
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: `ps-tryon-snap-step${analyzingDone ? " ps-active" : ""}`, children: [
17877
- /* @__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" } }) }),
17878
- /* @__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") })
17879
18361
  ] })
17880
- ] }),
17881
- tryOnProcessing && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: `ps-tryon-snap-step${tryOnDone ? " ps-done" : " ps-active"}`, children: [
17882
- /* @__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" } }) }),
17883
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: t2("Generating virtual try-on") })
17884
- ] })
17885
- ] })
18362
+ ] });
18363
+ })()
17886
18364
  ] }),
17887
18365
  (allDone || sizingResult && !isSnapProcessing) && /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
17888
18366
  isMultiSection ? activeSection ? (
@@ -18681,9 +19159,18 @@ function CreateProfileWizard({ onSave, onCancel, onPhotoPreview, onEstimate, t:
18681
19159
  const photoInputRef = reactExports.useRef(null);
18682
19160
  const nameInputRef = reactExports.useRef(null);
18683
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
+ };
18684
19167
  const handlePhotoSelect = async (e) => {
18685
19168
  const file = e.target.files?.[0];
18686
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
+ }
18687
19174
  if (!file.type.startsWith("image/")) {
18688
19175
  setError(t2("Please upload an image file"));
18689
19176
  return;
@@ -19264,18 +19751,71 @@ function CreateProfileWizard({ onSave, onCancel, onPhotoPreview, onEstimate, t:
19264
19751
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-cpw-image-left", children: photoBase64 ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-cpw-photo-preview-frame", children: [
19265
19752
  /* @__PURE__ */ jsxRuntimeExports.jsx("img", { src: photoBase64, alt: t2("Profile photo"), className: "ps-cpw-photo-preview-img" }),
19266
19753
  /* @__PURE__ */ jsxRuntimeExports.jsx("button", { type: "button", className: "ps-cpw-photo-remove", onClick: handleRemovePhoto, "aria-label": t2("Remove photo"), children: "×" }),
19267
- /* @__PURE__ */ jsxRuntimeExports.jsx("button", { type: "button", className: "ps-cpw-photo-retake-pill", onClick: () => photoInputRef.current?.click(), children: t2("Retake") })
19268
- ] }) : /* @__PURE__ */ jsxRuntimeExports.jsxs("button", { type: "button", className: "ps-cpw-dropzone", onClick: () => photoInputRef.current?.click(), disabled: photoUploading, children: [
19269
- /* @__PURE__ */ jsxRuntimeExports.jsx("img", { src: photoUploadIllustrationImg, alt: "", "aria-hidden": "true", className: "ps-cpw-dropzone-silhouette" }),
19270
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-cpw-dropzone-content", children: [
19271
- /* @__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: [
19272
- /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" }),
19273
- /* @__PURE__ */ jsxRuntimeExports.jsx("polyline", { points: "17 8 12 3 7 8" }),
19274
- /* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: "12", y1: "3", x2: "12", y2: "15" })
19275
- ] }),
19276
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "ps-cpw-dropzone-title", children: photoUploading ? t2("Processing...") : t2("Drop a photo or click to upload") }),
19277
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "ps-cpw-dropzone-hint", children: t2("JPEG · PNG · WebP · up to 10MB") })
19278
- ] })
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
+ ] }) })
19279
19819
  ] }) }),
19280
19820
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-cpw-image-right", children: [
19281
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: [
@@ -19306,6 +19846,13 @@ function CreateProfileWizard({ onSave, onCancel, onPhotoPreview, onEstimate, t:
19306
19846
  ] })
19307
19847
  ] })
19308
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
+ ] }),
19309
19856
  error && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-cpw-error", children: error })
19310
19857
  ] })
19311
19858
  ] }, "image-photo"),
@@ -20149,6 +20696,8 @@ function PhotoStepMobile({
20149
20696
  const isCloseUp = photoVariant === "close-up";
20150
20697
  const fileRef = reactExports.useRef(null);
20151
20698
  const hasPhoto = !!photoPreview;
20699
+ const [ageConfirmed, setAgeConfirmed] = reactExports.useState(null);
20700
+ const gated = !hasPhoto && ageConfirmed !== true;
20152
20701
  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-pm-root", children: [
20153
20702
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-pm-header", children: [
20154
20703
  /* @__PURE__ */ jsxRuntimeExports.jsx("h2", { className: "ps-pm-title", children: isCloseUp ? t2("Upload a face photo or selfie") : t2("Review your photo") }),
@@ -20176,19 +20725,46 @@ function PhotoStepMobile({
20176
20725
  children: /* @__PURE__ */ jsxRuntimeExports.jsx(CloseIconSm, {})
20177
20726
  }
20178
20727
  )
20179
- ] }) : /* @__PURE__ */ jsxRuntimeExports.jsxs(
20180
- "button",
20181
- {
20182
- type: "button",
20183
- className: "ps-pm-preview-empty",
20184
- onClick: () => fileRef.current?.click(),
20185
- children: [
20186
- /* @__PURE__ */ jsxRuntimeExports.jsx(UploadIconLg, {}),
20187
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "ps-pm-preview-empty-title", children: t2("Tap to upload") }),
20188
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "ps-pm-preview-empty-hint", children: t2("JPEG, PNG up to 10MB") })
20189
- ]
20190
- }
20191
- ) }),
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
+ ] }),
20192
20768
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-pm-checklist", children: [
20193
20769
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-pm-checklist-icon", children: /* @__PURE__ */ jsxRuntimeExports.jsx(InfoIcon, {}) }),
20194
20770
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-pm-checklist-body", children: [
@@ -21542,7 +22118,6 @@ function HeadSizeView(props) {
21542
22118
  title: "Headwear Measurements",
21543
22119
  fields,
21544
22120
  photoVariant: "close-up",
21545
- disablePhotoUpload: true,
21546
22121
  ...rest
21547
22122
  }
21548
22123
  );
@@ -21602,7 +22177,6 @@ function FaceSizeView(props) {
21602
22177
  fields,
21603
22178
  unitOptions: EYEWEAR_UNIT_OPTIONS,
21604
22179
  photoVariant: "close-up",
21605
- disablePhotoUpload: true,
21606
22180
  ...rest
21607
22181
  }
21608
22182
  );
@@ -21712,6 +22286,7 @@ function PrimeStyleTryonInner({
21712
22286
  const bodyRef = reactExports.useRef(null);
21713
22287
  const modelPoseRef = reactExports.useRef(null);
21714
22288
  const [bodyLandmarks, setBodyLandmarks] = reactExports.useState(null);
22289
+ const [faceLandmarks, setFaceLandmarks] = reactExports.useState(null);
21715
22290
  const selectedFileRef = reactExports.useRef(null);
21716
22291
  reactExports.useEffect(() => {
21717
22292
  try {
@@ -22290,6 +22865,55 @@ function PrimeStyleTryonInner({
22290
22865
  setSizingLoading(true);
22291
22866
  setEstimationDone(false);
22292
22867
  setView("size-result");
22868
+ const measurementType = detectMeasurementType(productTitle);
22869
+ if (measurementType === "face" || measurementType === "head") {
22870
+ setFaceLandmarks(null);
22871
+ try {
22872
+ const faceResult = await detectFaceMeasurements(objUrl);
22873
+ if (faceResult) setFaceLandmarks(faceResult.landmarks);
22874
+ const facePayload = {
22875
+ product: { title: productTitle },
22876
+ sizeGuide: sizeGuide ?? { found: false },
22877
+ sizingUnit: measurementType === "head" ? "cm" : "mm",
22878
+ category: measurementType,
22879
+ bodyImage: data.photoBase64
22880
+ };
22881
+ if (faceResult) {
22882
+ facePayload.faceMeasurementsMm = faceResult.measurementsMm;
22883
+ facePayload.faceLandmarks = faceResult.landmarks;
22884
+ facePayload.irisConfidence = faceResult.irisConfidence;
22885
+ }
22886
+ const recRes = await fetch(`${baseUrl}/api/v1/sizing/face-recommend`, {
22887
+ method: "POST",
22888
+ headers: { "Content-Type": "application/json", Authorization: `Bearer ${key}` },
22889
+ body: JSON.stringify(facePayload)
22890
+ });
22891
+ if (recRes.ok) {
22892
+ const recData = await recRes.json();
22893
+ setSizingResult(recData);
22894
+ onComplete?.(recData);
22895
+ persistResultToProfile(
22896
+ {
22897
+ gender: data.gender,
22898
+ height: data.height,
22899
+ weight: data.weight,
22900
+ heightUnit: data.heightUnit,
22901
+ weightUnit: data.weightUnit,
22902
+ age: data.age,
22903
+ bodyImage: data.photoBase64
22904
+ },
22905
+ recData
22906
+ );
22907
+ } else {
22908
+ setEstimationDone(true);
22909
+ }
22910
+ } catch (err) {
22911
+ console.error("[ps-sdk] face-recommend failed:", err);
22912
+ setEstimationDone(true);
22913
+ }
22914
+ setSizingLoading(false);
22915
+ return;
22916
+ }
22293
22917
  modelPoseRef.current = null;
22294
22918
  setBodyLandmarks(null);
22295
22919
  detectMeasurementLines(objUrl).then((lines) => {
@@ -22900,6 +23524,8 @@ function PrimeStyleTryonInner({
22900
23524
  handleTryOnSubmit,
22901
23525
  tryOnProcessing,
22902
23526
  bodyLandmarks,
23527
+ faceLandmarks,
23528
+ measurementType: detectMeasurementType(productTitle),
22903
23529
  activeSection,
22904
23530
  setActiveSection,
22905
23531
  onResetTryOn: () => {