@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
|
-
|
|
17867
|
-
|
|
17868
|
-
|
|
17869
|
-
|
|
17870
|
-
|
|
17871
|
-
|
|
17872
|
-
|
|
17873
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("
|
|
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(
|
|
17877
|
-
/* @__PURE__ */ jsxRuntimeExports.
|
|
17878
|
-
|
|
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
|
-
|
|
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:
|
|
19268
|
-
] }) : /* @__PURE__ */ jsxRuntimeExports.jsxs("
|
|
19269
|
-
/* @__PURE__ */ jsxRuntimeExports.
|
|
19270
|
-
|
|
19271
|
-
|
|
19272
|
-
|
|
19273
|
-
|
|
19274
|
-
|
|
19275
|
-
|
|
19276
|
-
|
|
19277
|
-
|
|
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
|
-
|
|
20181
|
-
|
|
20182
|
-
|
|
20183
|
-
|
|
20184
|
-
|
|
20185
|
-
|
|
20186
|
-
|
|
20187
|
-
|
|
20188
|
-
|
|
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: () => {
|