@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.
package/dist/react/index.js
CHANGED
|
@@ -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
|
-
|
|
8443
|
-
|
|
8444
|
-
|
|
8445
|
-
|
|
8446
|
-
|
|
8447
|
-
|
|
8448
|
-
|
|
8449
|
-
/* @__PURE__ */ jsx("
|
|
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(
|
|
8453
|
-
/* @__PURE__ */
|
|
8454
|
-
|
|
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
|
-
|
|
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:
|
|
9844
|
-
] }) : /* @__PURE__ */ jsxs("
|
|
9845
|
-
/* @__PURE__ */
|
|
9846
|
-
|
|
9847
|
-
|
|
9848
|
-
|
|
9849
|
-
|
|
9850
|
-
|
|
9851
|
-
|
|
9852
|
-
|
|
9853
|
-
|
|
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
|
-
|
|
10757
|
-
|
|
10758
|
-
|
|
10759
|
-
|
|
10760
|
-
|
|
10761
|
-
|
|
10762
|
-
|
|
10763
|
-
|
|
10764
|
-
|
|
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: () => {
|