@primestyleai/tryon 5.10.100 → 5.10.102
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.
|
@@ -7,8 +7,11 @@ import type { TranslateFn } from "../../i18n";
|
|
|
7
7
|
* Desktop: split — photo upload on the left, Do/Don't/Pro-Tip on the right.
|
|
8
8
|
* Mobile: stacked — title + preview + checklist + START TRY-ON.
|
|
9
9
|
*/
|
|
10
|
-
export declare function PhotoGuideView({ measurementType, onBack, onSubmit, t, }: {
|
|
10
|
+
export declare function PhotoGuideView({ measurementType, apiUrl, apiKey, onBack, onSubmit, t, }: {
|
|
11
11
|
measurementType?: "body" | "face" | "head" | "foot";
|
|
12
|
+
/** Backend URL for the age-check call (passed through to PhotoUploadZone). */
|
|
13
|
+
apiUrl?: string;
|
|
14
|
+
apiKey?: string;
|
|
12
15
|
onBack: () => void;
|
|
13
16
|
onSubmit: (file: File) => void;
|
|
14
17
|
t: TranslateFn;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { TranslateFn } from "../../i18n";
|
|
2
|
+
/**
|
|
3
|
+
* Single source of truth for photo upload UX across the SDK. Handles:
|
|
4
|
+
* • drag / drop (with proper enter+over+leave+drop handlers — drop fails
|
|
5
|
+
* silently in Chromium without preventDefault on every event)
|
|
6
|
+
* • click-to-open file picker
|
|
7
|
+
* • optional Gemini age-check (when apiUrl + apiKey are supplied)
|
|
8
|
+
* • size + type validation
|
|
9
|
+
* • internal preview, processing spinner, rejection card
|
|
10
|
+
*
|
|
11
|
+
* Used by PhotoGuideView, SizeResultView's photo-guide overlay, and any
|
|
12
|
+
* other place a customer is asked for a photo. Replaces the duplicated
|
|
13
|
+
* handlePhotoSelect blocks in BodyProfileView / CreateProfileWizard /
|
|
14
|
+
* AccessorySizeView (incrementally — those still have their own state).
|
|
15
|
+
*/
|
|
16
|
+
export type PhotoUploadZoneVariant = "block" | "inline";
|
|
17
|
+
export declare function PhotoUploadZone({ apiUrl, apiKey, previewUrl, requireAgeConfirm, ageConfirmed, disabled, hintLine, emptyTitle, variant, onPhotoAccepted, onClearPhoto, t, }: {
|
|
18
|
+
/** When provided with apiKey, runs Gemini age-check before accepting. */
|
|
19
|
+
apiUrl?: string;
|
|
20
|
+
apiKey?: string;
|
|
21
|
+
/** External preview URL (e.g. parent already has a previewUrl from an
|
|
22
|
+
* earlier flow). Pass null to render the empty drop state. */
|
|
23
|
+
previewUrl?: string | null;
|
|
24
|
+
/** When true, the zone refuses uploads until ageConfirmed is true. */
|
|
25
|
+
requireAgeConfirm?: boolean;
|
|
26
|
+
ageConfirmed?: boolean;
|
|
27
|
+
/** Disables click + drop (e.g. while parent is processing). */
|
|
28
|
+
disabled?: boolean;
|
|
29
|
+
/** Optional copy under the empty-state title. */
|
|
30
|
+
hintLine?: string;
|
|
31
|
+
/** Custom empty-state title. Defaults to "Upload your photo". */
|
|
32
|
+
emptyTitle?: string;
|
|
33
|
+
/** "block" (default) — full upload card with title/hint/icon.
|
|
34
|
+
* "inline" — just the drop area sized to its parent (preview-only). */
|
|
35
|
+
variant?: PhotoUploadZoneVariant;
|
|
36
|
+
/** Called with the validated File once it passes age-check + size/type
|
|
37
|
+
* validation. Parent is responsible for compressing or sending it. */
|
|
38
|
+
onPhotoAccepted: (file: File) => void;
|
|
39
|
+
/** Called when the user removes the current photo (via the X on preview). */
|
|
40
|
+
onClearPhoto?: () => void;
|
|
41
|
+
t: TranslateFn;
|
|
42
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
@@ -17929,6 +17929,110 @@ const STYLES$1 = `
|
|
|
17929
17929
|
|
|
17930
17930
|
/* Upload hover overlay */
|
|
17931
17931
|
.ps-tryon-upload-hover:hover .ps-tryon-upload-hover-overlay { opacity: 1 !important; }
|
|
17932
|
+
|
|
17933
|
+
/* ─────────── Unified PhotoUploadZone ─────────── */
|
|
17934
|
+
.ps-photo-zone {
|
|
17935
|
+
flex: 1; min-height: 220px;
|
|
17936
|
+
display: flex; flex-direction: column; align-items: center; justify-content: center;
|
|
17937
|
+
border: 2px dashed var(--ps-border-color);
|
|
17938
|
+
border-radius: 0.5vw;
|
|
17939
|
+
background: var(--ps-bg-secondary);
|
|
17940
|
+
cursor: pointer; position: relative; overflow: hidden;
|
|
17941
|
+
transition: border-color 0.18s, background 0.18s, transform 0.18s;
|
|
17942
|
+
}
|
|
17943
|
+
.ps-photo-zone:hover { border-color: var(--ps-accent); background: rgba(33,84,239,0.02); }
|
|
17944
|
+
.ps-photo-zone.ps-photo-zone-drag {
|
|
17945
|
+
border-color: var(--ps-accent); border-style: solid;
|
|
17946
|
+
background: rgba(33,84,239,0.06);
|
|
17947
|
+
transform: scale(1.005);
|
|
17948
|
+
}
|
|
17949
|
+
.ps-photo-zone.ps-photo-zone-has { border: none; cursor: default; padding: 0; }
|
|
17950
|
+
.ps-photo-zone.ps-photo-zone-inline { min-height: 100%; height: 100%; }
|
|
17951
|
+
|
|
17952
|
+
.ps-photo-zone-empty {
|
|
17953
|
+
display: flex; flex-direction: column; align-items: center; gap: 0.4vw;
|
|
17954
|
+
padding: 1vw; text-align: center; pointer-events: none;
|
|
17955
|
+
}
|
|
17956
|
+
.ps-photo-zone-title { font-size: 0.85vw; font-weight: 600; color: var(--ps-text-primary); }
|
|
17957
|
+
.ps-photo-zone-hint { font-size: 0.6vw; color: var(--ps-text-muted); line-height: 1.4; max-width: 24vw; }
|
|
17958
|
+
|
|
17959
|
+
.ps-photo-zone-img {
|
|
17960
|
+
width: 100%; height: 100%; object-fit: contain;
|
|
17961
|
+
display: block; cursor: pointer;
|
|
17962
|
+
}
|
|
17963
|
+
.ps-photo-zone-hover-overlay {
|
|
17964
|
+
position: absolute; inset: 0;
|
|
17965
|
+
display: flex; align-items: center; justify-content: center;
|
|
17966
|
+
background: rgba(0,0,0,0.4); opacity: 0; transition: opacity 0.2s;
|
|
17967
|
+
color: #fff; font-size: 0.8vw; font-weight: 600;
|
|
17968
|
+
border-radius: 0.5vw; cursor: pointer;
|
|
17969
|
+
}
|
|
17970
|
+
.ps-photo-zone:hover .ps-photo-zone-hover-overlay { opacity: 1; }
|
|
17971
|
+
.ps-photo-zone-remove {
|
|
17972
|
+
position: absolute; top: 0.5vw; right: 0.5vw;
|
|
17973
|
+
background: rgba(0,0,0,0.55); color: #fff; border: none; border-radius: 50%;
|
|
17974
|
+
width: 24px; height: 24px; min-width: 24px;
|
|
17975
|
+
display: flex; align-items: center; justify-content: center;
|
|
17976
|
+
cursor: pointer; transition: background 0.15s;
|
|
17977
|
+
z-index: 2;
|
|
17978
|
+
}
|
|
17979
|
+
.ps-photo-zone-remove:hover { background: rgba(239,68,68,0.85); }
|
|
17980
|
+
|
|
17981
|
+
.ps-photo-zone-processing {
|
|
17982
|
+
display: flex; flex-direction: column; align-items: center; gap: 0.5vw;
|
|
17983
|
+
padding: 1vw;
|
|
17984
|
+
}
|
|
17985
|
+
.ps-photo-zone-spinner {
|
|
17986
|
+
width: 28px; height: 28px;
|
|
17987
|
+
border: 3px solid rgba(33,84,239,0.18);
|
|
17988
|
+
border-top-color: var(--ps-accent);
|
|
17989
|
+
border-radius: 50%;
|
|
17990
|
+
animation: ps-spin 0.7s linear infinite;
|
|
17991
|
+
}
|
|
17992
|
+
.ps-photo-zone-status { font-size: 0.7vw; color: var(--ps-text-secondary); }
|
|
17993
|
+
|
|
17994
|
+
.ps-photo-zone-rejection {
|
|
17995
|
+
display: flex; flex-direction: column; align-items: center; gap: 0.5vw;
|
|
17996
|
+
padding: 1vw 1.2vw; max-width: 22vw; text-align: center;
|
|
17997
|
+
cursor: default;
|
|
17998
|
+
}
|
|
17999
|
+
.ps-photo-zone-rejection-icon {
|
|
18000
|
+
width: 36px; height: 36px; border-radius: 50%;
|
|
18001
|
+
background: rgba(239,68,68,0.12); color: #dc2626;
|
|
18002
|
+
display: flex; align-items: center; justify-content: center;
|
|
18003
|
+
font-size: 18px; font-weight: 700;
|
|
18004
|
+
}
|
|
18005
|
+
.ps-photo-zone-rejection-title { font-size: 0.85vw; font-weight: 700; color: var(--ps-text-primary); }
|
|
18006
|
+
.ps-photo-zone-rejection-msg { font-size: 0.65vw; color: var(--ps-text-secondary); line-height: 1.5; }
|
|
18007
|
+
.ps-photo-zone-rejection-cta {
|
|
18008
|
+
margin-top: 0.3vw; padding: 0.55vw 1vw;
|
|
18009
|
+
background: var(--ps-accent); color: #fff; border: none;
|
|
18010
|
+
border-radius: 0.4vw; font-family: inherit;
|
|
18011
|
+
font-size: 0.7vw; font-weight: 600; cursor: pointer;
|
|
18012
|
+
transition: opacity 0.15s;
|
|
18013
|
+
}
|
|
18014
|
+
.ps-photo-zone-rejection-cta:hover { opacity: 0.9; }
|
|
18015
|
+
|
|
18016
|
+
.ps-photo-zone-error {
|
|
18017
|
+
position: absolute; bottom: 0.6vw; left: 0.6vw; right: 0.6vw;
|
|
18018
|
+
background: rgba(239,68,68,0.08); color: #dc2626;
|
|
18019
|
+
border: 1px solid rgba(239,68,68,0.2); border-radius: 0.4vw;
|
|
18020
|
+
padding: 0.4vw 0.6vw; font-size: 0.6vw; line-height: 1.4;
|
|
18021
|
+
text-align: center; pointer-events: none;
|
|
18022
|
+
}
|
|
18023
|
+
|
|
18024
|
+
@media (max-width: 700px) {
|
|
18025
|
+
.ps-photo-zone { min-height: 200px; border-radius: 12px; }
|
|
18026
|
+
.ps-photo-zone-title { font-size: 14px; }
|
|
18027
|
+
.ps-photo-zone-hint { font-size: 11px; max-width: 90%; }
|
|
18028
|
+
.ps-photo-zone-hover-overlay { font-size: 13px; }
|
|
18029
|
+
.ps-photo-zone-status { font-size: 12px; }
|
|
18030
|
+
.ps-photo-zone-rejection { max-width: 90%; gap: 8px; padding: 14px; }
|
|
18031
|
+
.ps-photo-zone-rejection-title { font-size: 14px; }
|
|
18032
|
+
.ps-photo-zone-rejection-msg { font-size: 12px; }
|
|
18033
|
+
.ps-photo-zone-rejection-cta { font-size: 13px; padding: 10px 16px; border-radius: 8px; }
|
|
18034
|
+
.ps-photo-zone-error { font-size: 11px; padding: 8px 10px; border-radius: 8px; }
|
|
18035
|
+
}
|
|
17932
18036
|
`;
|
|
17933
18037
|
function CameraIcon$1({ size = 18 }) {
|
|
17934
18038
|
return /* @__PURE__ */ jsxRuntimeExports.jsxs("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round", children: [
|
|
@@ -18675,26 +18779,19 @@ function EngagingTryOnView({
|
|
|
18675
18779
|
}, 200);
|
|
18676
18780
|
return () => clearInterval(id2);
|
|
18677
18781
|
}, []);
|
|
18678
|
-
const
|
|
18679
|
-
t2("Our model is analyzing 150+ body landmarks for the perfect fit"),
|
|
18680
|
-
t2("Calibrating fabric drape against your body proportions"),
|
|
18681
|
-
t2("Cross-checking fit against millions of garment patterns"),
|
|
18682
|
-
t2("Rendering shadows and highlights to match your photo's lighting")
|
|
18683
|
-
];
|
|
18782
|
+
const aiFact = t2("Our model is analyzing 150+ body landmarks for the perfect fit");
|
|
18684
18783
|
const styleTips = [
|
|
18685
|
-
t2("
|
|
18686
|
-
t2("
|
|
18687
|
-
t2("Cuff
|
|
18784
|
+
t2("Match your belt to your shoes — never your pants"),
|
|
18785
|
+
t2("Leave the bottom button of a suit jacket undone"),
|
|
18786
|
+
t2("Cuff a pocket square so it peeks 1–2 cm above the pocket"),
|
|
18787
|
+
t2("Roll sleeves twice for a relaxed, intentional finish"),
|
|
18788
|
+
t2("A tie tip should land at the middle of your belt buckle"),
|
|
18789
|
+
t2("Cufflinks should sit half an inch past the jacket sleeve")
|
|
18688
18790
|
];
|
|
18689
|
-
const [factIdx, setFactIdx] = reactExports.useState(0);
|
|
18690
18791
|
const [tipIdx, setTipIdx] = reactExports.useState(0);
|
|
18691
18792
|
reactExports.useEffect(() => {
|
|
18692
|
-
const f2 = setInterval(() => setFactIdx((i) => (i + 1) % aiFacts.length), 4e3);
|
|
18693
18793
|
const s = setInterval(() => setTipIdx((i) => (i + 1) % styleTips.length), 5e3);
|
|
18694
|
-
return () =>
|
|
18695
|
-
clearInterval(f2);
|
|
18696
|
-
clearInterval(s);
|
|
18697
|
-
};
|
|
18794
|
+
return () => clearInterval(s);
|
|
18698
18795
|
}, []);
|
|
18699
18796
|
const garmentLine = productMaterial?.trim() || (productDescription?.trim() ? productDescription.trim().slice(0, 90) + (productDescription.trim().length > 90 ? "…" : "") : null) || t2("Crafted with quality materials for everyday comfort");
|
|
18700
18797
|
const Panel = /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-tryon-processing-v2-panel", children: [
|
|
@@ -18747,7 +18844,7 @@ function EngagingTryOnView({
|
|
|
18747
18844
|
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-tryon-processing-v2-card-icon ps-fact", children: /* @__PURE__ */ jsxRuntimeExports.jsx("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M12 2v6M12 16v6M4.93 4.93l4.24 4.24M14.83 14.83l4.24 4.24M2 12h6M16 12h6M4.93 19.07l4.24-4.24M14.83 9.17l4.24-4.24" }) }) }),
|
|
18748
18845
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-tryon-processing-v2-card-text", children: [
|
|
18749
18846
|
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-tryon-processing-v2-card-head", children: t2("AI Fact") }),
|
|
18750
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-tryon-processing-v2-card-body", children:
|
|
18847
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-tryon-processing-v2-card-body", children: aiFact })
|
|
18751
18848
|
] })
|
|
18752
18849
|
] })
|
|
18753
18850
|
] })
|
|
@@ -21928,8 +22025,191 @@ function UploadView({
|
|
|
21928
22025
|
}
|
|
21929
22026
|
) });
|
|
21930
22027
|
}
|
|
22028
|
+
function PhotoUploadZone({
|
|
22029
|
+
apiUrl,
|
|
22030
|
+
apiKey,
|
|
22031
|
+
previewUrl,
|
|
22032
|
+
requireAgeConfirm = false,
|
|
22033
|
+
ageConfirmed,
|
|
22034
|
+
disabled = false,
|
|
22035
|
+
hintLine,
|
|
22036
|
+
emptyTitle,
|
|
22037
|
+
variant = "block",
|
|
22038
|
+
onPhotoAccepted,
|
|
22039
|
+
onClearPhoto,
|
|
22040
|
+
t: t2
|
|
22041
|
+
}) {
|
|
22042
|
+
const inputRef = reactExports.useRef(null);
|
|
22043
|
+
const dragDepthRef = reactExports.useRef(0);
|
|
22044
|
+
const [dragOver, setDragOver] = reactExports.useState(false);
|
|
22045
|
+
const [processing, setProcessing] = reactExports.useState(false);
|
|
22046
|
+
const [statusText, setStatusText] = reactExports.useState("");
|
|
22047
|
+
const [rejection, setRejection] = reactExports.useState(null);
|
|
22048
|
+
const [localError, setLocalError] = reactExports.useState(null);
|
|
22049
|
+
reactExports.useEffect(() => {
|
|
22050
|
+
if (!previewUrl) {
|
|
22051
|
+
setRejection(null);
|
|
22052
|
+
setLocalError(null);
|
|
22053
|
+
}
|
|
22054
|
+
}, [previewUrl]);
|
|
22055
|
+
const acceptFile = async (file) => {
|
|
22056
|
+
if (disabled || processing) return;
|
|
22057
|
+
setLocalError(null);
|
|
22058
|
+
setRejection(null);
|
|
22059
|
+
if (requireAgeConfirm && ageConfirmed !== true) {
|
|
22060
|
+
setLocalError(t2("Please confirm that the person in the photo is 18 or older before uploading."));
|
|
22061
|
+
return;
|
|
22062
|
+
}
|
|
22063
|
+
if (!file.type.startsWith("image/")) {
|
|
22064
|
+
setLocalError(t2("Please upload an image file"));
|
|
22065
|
+
return;
|
|
22066
|
+
}
|
|
22067
|
+
if (file.size > 10 * 1024 * 1024) {
|
|
22068
|
+
setLocalError(t2("Image must be under 10MB"));
|
|
22069
|
+
return;
|
|
22070
|
+
}
|
|
22071
|
+
if (apiUrl && apiKey) {
|
|
22072
|
+
setProcessing(true);
|
|
22073
|
+
setStatusText(t2("Analyzing photo…"));
|
|
22074
|
+
try {
|
|
22075
|
+
const ageResult = await checkAgeBeforeUpload(file, apiUrl, apiKey);
|
|
22076
|
+
if (!ageResult.isAdult) {
|
|
22077
|
+
const reason = ageResult.reasoning?.trim() || t2("This photo appears to be of a minor. Please upload a photo of someone 18 or older.");
|
|
22078
|
+
setRejection(reason);
|
|
22079
|
+
setProcessing(false);
|
|
22080
|
+
setStatusText("");
|
|
22081
|
+
return;
|
|
22082
|
+
}
|
|
22083
|
+
try {
|
|
22084
|
+
await compressImage(file, { maxDimension: 1024, quality: 0.85 });
|
|
22085
|
+
} catch {
|
|
22086
|
+
}
|
|
22087
|
+
} catch {
|
|
22088
|
+
} finally {
|
|
22089
|
+
setProcessing(false);
|
|
22090
|
+
setStatusText("");
|
|
22091
|
+
}
|
|
22092
|
+
}
|
|
22093
|
+
onPhotoAccepted(file);
|
|
22094
|
+
};
|
|
22095
|
+
const onClick = () => {
|
|
22096
|
+
if (disabled || processing) return;
|
|
22097
|
+
inputRef.current?.click();
|
|
22098
|
+
};
|
|
22099
|
+
const onDragEnter = (e) => {
|
|
22100
|
+
e.preventDefault();
|
|
22101
|
+
e.stopPropagation();
|
|
22102
|
+
if (disabled || processing) return;
|
|
22103
|
+
dragDepthRef.current += 1;
|
|
22104
|
+
setDragOver(true);
|
|
22105
|
+
};
|
|
22106
|
+
const onDragOver = (e) => {
|
|
22107
|
+
e.preventDefault();
|
|
22108
|
+
e.stopPropagation();
|
|
22109
|
+
if (e.dataTransfer) e.dataTransfer.dropEffect = "copy";
|
|
22110
|
+
};
|
|
22111
|
+
const onDragLeave = (e) => {
|
|
22112
|
+
e.preventDefault();
|
|
22113
|
+
e.stopPropagation();
|
|
22114
|
+
dragDepthRef.current = Math.max(0, dragDepthRef.current - 1);
|
|
22115
|
+
if (dragDepthRef.current === 0) setDragOver(false);
|
|
22116
|
+
};
|
|
22117
|
+
const onDrop = (e) => {
|
|
22118
|
+
e.preventDefault();
|
|
22119
|
+
e.stopPropagation();
|
|
22120
|
+
dragDepthRef.current = 0;
|
|
22121
|
+
setDragOver(false);
|
|
22122
|
+
if (disabled || processing) return;
|
|
22123
|
+
const file = e.dataTransfer?.files?.[0];
|
|
22124
|
+
if (file) void acceptFile(file);
|
|
22125
|
+
};
|
|
22126
|
+
const onChange = (e) => {
|
|
22127
|
+
const file = e.target.files?.[0];
|
|
22128
|
+
if (file) void acceptFile(file);
|
|
22129
|
+
if (inputRef.current) inputRef.current.value = "";
|
|
22130
|
+
};
|
|
22131
|
+
const removePhoto = () => {
|
|
22132
|
+
setRejection(null);
|
|
22133
|
+
setLocalError(null);
|
|
22134
|
+
onClearPhoto?.();
|
|
22135
|
+
};
|
|
22136
|
+
const isInline = variant === "inline";
|
|
22137
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
22138
|
+
"div",
|
|
22139
|
+
{
|
|
22140
|
+
className: `ps-photo-zone${dragOver ? " ps-photo-zone-drag" : ""}${isInline ? " ps-photo-zone-inline" : ""}${previewUrl ? " ps-photo-zone-has" : ""}`,
|
|
22141
|
+
onClick: previewUrl ? void 0 : onClick,
|
|
22142
|
+
onDragEnter,
|
|
22143
|
+
onDragOver,
|
|
22144
|
+
onDragLeave,
|
|
22145
|
+
onDrop,
|
|
22146
|
+
children: [
|
|
22147
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
22148
|
+
"input",
|
|
22149
|
+
{
|
|
22150
|
+
ref: inputRef,
|
|
22151
|
+
type: "file",
|
|
22152
|
+
accept: "image/jpeg,image/png,image/webp",
|
|
22153
|
+
style: { display: "none" },
|
|
22154
|
+
onChange
|
|
22155
|
+
}
|
|
22156
|
+
),
|
|
22157
|
+
previewUrl && !rejection && /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
|
|
22158
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("img", { src: previewUrl, alt: t2("Your photo"), className: "ps-photo-zone-img" }),
|
|
22159
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-photo-zone-hover-overlay", onClick, title: t2("Click to change photo"), children: /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: t2("Click to change photo") }) }),
|
|
22160
|
+
onClearPhoto && /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
22161
|
+
"button",
|
|
22162
|
+
{
|
|
22163
|
+
type: "button",
|
|
22164
|
+
className: "ps-photo-zone-remove",
|
|
22165
|
+
onClick: (e) => {
|
|
22166
|
+
e.stopPropagation();
|
|
22167
|
+
removePhoto();
|
|
22168
|
+
},
|
|
22169
|
+
"aria-label": t2("Remove photo"),
|
|
22170
|
+
children: /* @__PURE__ */ jsxRuntimeExports.jsxs("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", width: "14", height: "14", children: [
|
|
22171
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
|
|
22172
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: "6", y1: "6", x2: "18", y2: "18" })
|
|
22173
|
+
] })
|
|
22174
|
+
}
|
|
22175
|
+
)
|
|
22176
|
+
] }),
|
|
22177
|
+
!previewUrl && !rejection && !processing && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-photo-zone-empty", children: [
|
|
22178
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(UploadIcon, { size: 32 }),
|
|
22179
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "ps-photo-zone-title", children: emptyTitle || t2("Upload your photo") }),
|
|
22180
|
+
hintLine && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "ps-photo-zone-hint", children: hintLine }),
|
|
22181
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "ps-photo-zone-hint", children: t2("JPEG, PNG up to 10MB · click or drop") })
|
|
22182
|
+
] }),
|
|
22183
|
+
processing && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-photo-zone-processing", children: [
|
|
22184
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-photo-zone-spinner" }),
|
|
22185
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "ps-photo-zone-status", children: statusText })
|
|
22186
|
+
] }),
|
|
22187
|
+
rejection && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-photo-zone-rejection", onClick: (e) => e.stopPropagation(), children: [
|
|
22188
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-photo-zone-rejection-icon", "aria-hidden": "true", children: "!" }),
|
|
22189
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-photo-zone-rejection-title", children: t2("Different photo needed") }),
|
|
22190
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-photo-zone-rejection-msg", children: rejection }),
|
|
22191
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
22192
|
+
"button",
|
|
22193
|
+
{
|
|
22194
|
+
type: "button",
|
|
22195
|
+
className: "ps-photo-zone-rejection-cta",
|
|
22196
|
+
onClick: () => {
|
|
22197
|
+
setRejection(null);
|
|
22198
|
+
inputRef.current?.click();
|
|
22199
|
+
},
|
|
22200
|
+
children: t2("Choose another photo")
|
|
22201
|
+
}
|
|
22202
|
+
)
|
|
22203
|
+
] }),
|
|
22204
|
+
localError && !rejection && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-photo-zone-error", onClick: (e) => e.stopPropagation(), children: localError })
|
|
22205
|
+
]
|
|
22206
|
+
}
|
|
22207
|
+
);
|
|
22208
|
+
}
|
|
21931
22209
|
function PhotoGuideView({
|
|
21932
22210
|
measurementType = "body",
|
|
22211
|
+
apiUrl,
|
|
22212
|
+
apiKey,
|
|
21933
22213
|
onBack,
|
|
21934
22214
|
onSubmit,
|
|
21935
22215
|
t: t2
|
|
@@ -21937,8 +22217,6 @@ function PhotoGuideView({
|
|
|
21937
22217
|
const isMobile = useIsMobile();
|
|
21938
22218
|
const [guideFile, setGuideFile] = reactExports.useState(null);
|
|
21939
22219
|
const [guidePreviewUrl, setGuidePreviewUrl] = reactExports.useState(null);
|
|
21940
|
-
const [, setGuideDragOver] = reactExports.useState(false);
|
|
21941
|
-
const guideInputRef = reactExports.useRef(null);
|
|
21942
22220
|
reactExports.useEffect(() => {
|
|
21943
22221
|
if (!guideFile) {
|
|
21944
22222
|
setGuidePreviewUrl(null);
|
|
@@ -21958,49 +22236,17 @@ function PhotoGuideView({
|
|
|
21958
22236
|
/* @__PURE__ */ jsxRuntimeExports.jsx("h2", { className: "ps-pm-title", children: t2("Review your photo") }),
|
|
21959
22237
|
/* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "ps-pm-subtitle", children: measurementType === "face" ? t2("A clear, front-facing face photo — no glasses on — gives us the most accurate try-on.") : measurementType === "head" ? t2("Face the camera with your head and shoulders in frame, leaving space above your head.") : t2("Ensure your full body is visible for the most accurate virtual try-on.") })
|
|
21960
22238
|
] }),
|
|
21961
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
21962
|
-
|
|
21963
|
-
{
|
|
21964
|
-
ref: guideInputRef,
|
|
21965
|
-
type: "file",
|
|
21966
|
-
accept: "image/jpeg,image/png,image/webp",
|
|
21967
|
-
style: { display: "none" },
|
|
21968
|
-
onChange: (e) => {
|
|
21969
|
-
const f2 = e.target.files?.[0];
|
|
21970
|
-
if (f2) setGuideFile(f2);
|
|
21971
|
-
}
|
|
21972
|
-
}
|
|
21973
|
-
),
|
|
21974
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-pm-preview", children: guideFile && guidePreviewUrl ? /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
|
|
21975
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("img", { src: guidePreviewUrl, alt: t2("Your photo"), className: "ps-pm-preview-img" }),
|
|
21976
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
21977
|
-
"button",
|
|
21978
|
-
{
|
|
21979
|
-
type: "button",
|
|
21980
|
-
className: "ps-pm-preview-remove",
|
|
21981
|
-
onClick: () => setGuideFile(null),
|
|
21982
|
-
"aria-label": t2("Remove photo"),
|
|
21983
|
-
children: /* @__PURE__ */ jsxRuntimeExports.jsxs("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", width: "14", height: "14", children: [
|
|
21984
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
|
|
21985
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: "6", y1: "6", x2: "18", y2: "18" })
|
|
21986
|
-
] })
|
|
21987
|
-
}
|
|
21988
|
-
)
|
|
21989
|
-
] }) : /* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
21990
|
-
"button",
|
|
22239
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-pm-preview", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
22240
|
+
PhotoUploadZone,
|
|
21991
22241
|
{
|
|
21992
|
-
|
|
21993
|
-
|
|
21994
|
-
|
|
21995
|
-
|
|
21996
|
-
|
|
21997
|
-
|
|
21998
|
-
|
|
21999
|
-
|
|
22000
|
-
] }),
|
|
22001
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "ps-pm-preview-empty-title", children: t2("Tap to upload") }),
|
|
22002
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "ps-pm-preview-empty-hint", children: t2("JPEG, PNG up to 10MB") })
|
|
22003
|
-
]
|
|
22242
|
+
apiUrl,
|
|
22243
|
+
apiKey,
|
|
22244
|
+
previewUrl: guidePreviewUrl,
|
|
22245
|
+
variant: "inline",
|
|
22246
|
+
onPhotoAccepted: (f2) => setGuideFile(f2),
|
|
22247
|
+
onClearPhoto: () => setGuideFile(null),
|
|
22248
|
+
emptyTitle: t2("Tap to upload"),
|
|
22249
|
+
t: t2
|
|
22004
22250
|
}
|
|
22005
22251
|
) }),
|
|
22006
22252
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-pm-checklist", children: [
|
|
@@ -22039,7 +22285,7 @@ function PhotoGuideView({
|
|
|
22039
22285
|
{
|
|
22040
22286
|
type: "button",
|
|
22041
22287
|
className: "ps-pm-secondary-btn",
|
|
22042
|
-
onClick: () =>
|
|
22288
|
+
onClick: () => setGuideFile(null),
|
|
22043
22289
|
children: t2("RETAKE PHOTO")
|
|
22044
22290
|
}
|
|
22045
22291
|
) : /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
@@ -22056,60 +22302,20 @@ function PhotoGuideView({
|
|
|
22056
22302
|
}
|
|
22057
22303
|
return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { style: { display: "flex", flexDirection: "column", padding: "1.5vw", width: "100%", height: "100%", background: "var(--ps-bg-primary)", borderRadius: "0.8vw", overflow: "hidden" }, children: [
|
|
22058
22304
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { style: { display: "flex", gap: "1.2vw", flex: 1, alignItems: "stretch", minHeight: 0, overflow: "hidden" }, children: [
|
|
22059
|
-
/* @__PURE__ */ jsxRuntimeExports.
|
|
22060
|
-
|
|
22305
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { style: { flex: 1, display: "flex", minHeight: 0 }, children: /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
22306
|
+
PhotoUploadZone,
|
|
22061
22307
|
{
|
|
22062
|
-
|
|
22063
|
-
|
|
22064
|
-
|
|
22065
|
-
|
|
22066
|
-
|
|
22067
|
-
|
|
22068
|
-
|
|
22069
|
-
|
|
22070
|
-
|
|
22071
|
-
const f2 = e.dataTransfer.files[0];
|
|
22072
|
-
if (f2) setGuideFile(f2);
|
|
22073
|
-
},
|
|
22074
|
-
style: {
|
|
22075
|
-
flex: 1,
|
|
22076
|
-
display: "flex",
|
|
22077
|
-
flexDirection: "column",
|
|
22078
|
-
alignItems: "center",
|
|
22079
|
-
justifyContent: "center",
|
|
22080
|
-
border: guideFile ? "none" : "2px dashed var(--ps-border-color)",
|
|
22081
|
-
borderRadius: "0.5vw",
|
|
22082
|
-
cursor: "pointer",
|
|
22083
|
-
position: "relative",
|
|
22084
|
-
background: "var(--ps-bg-secondary)",
|
|
22085
|
-
overflow: "hidden"
|
|
22086
|
-
},
|
|
22087
|
-
className: "ps-tryon-upload-hover",
|
|
22088
|
-
children: [
|
|
22089
|
-
guideFile && guidePreviewUrl ? /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
|
|
22090
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("img", { src: guidePreviewUrl, alt: "preview", style: { width: "100%", height: "100%", objectFit: "contain" } }),
|
|
22091
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-tryon-upload-hover-overlay", style: { position: "absolute", inset: 0, display: "flex", alignItems: "center", justifyContent: "center", background: "rgba(0,0,0,0.4)", opacity: 0, transition: "opacity 0.2s", borderRadius: "0.5vw" }, children: /* @__PURE__ */ jsxRuntimeExports.jsx("span", { style: { color: "#fff", fontSize: "0.8vw", fontWeight: 600 }, children: t2("Click to change photo") }) })
|
|
22092
|
-
] }) : /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
|
|
22093
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(UploadIcon, { size: 32 }),
|
|
22094
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { style: { fontSize: "0.85vw", fontWeight: 600, color: "var(--ps-text-primary)", marginTop: "0.5vw" }, children: t2("Upload your photo") }),
|
|
22095
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { style: { fontSize: "0.6vw", color: "var(--ps-text-muted)", marginTop: "0.2vw" }, children: measurementType === "face" ? t2("Click or drag a close-up face photo") : measurementType === "head" ? t2("Click or drag a head-and-shoulders photo") : t2("Click or drag a full-body photo") })
|
|
22096
|
-
] }),
|
|
22097
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
22098
|
-
"input",
|
|
22099
|
-
{
|
|
22100
|
-
ref: guideInputRef,
|
|
22101
|
-
type: "file",
|
|
22102
|
-
accept: "image/jpeg,image/png,image/webp",
|
|
22103
|
-
style: { display: "none" },
|
|
22104
|
-
onChange: (e) => {
|
|
22105
|
-
const f2 = e.target.files?.[0];
|
|
22106
|
-
if (f2) setGuideFile(f2);
|
|
22107
|
-
}
|
|
22108
|
-
}
|
|
22109
|
-
)
|
|
22110
|
-
]
|
|
22308
|
+
apiUrl,
|
|
22309
|
+
apiKey,
|
|
22310
|
+
previewUrl: guidePreviewUrl,
|
|
22311
|
+
variant: "inline",
|
|
22312
|
+
emptyTitle: t2("Upload your photo"),
|
|
22313
|
+
hintLine: measurementType === "face" ? t2("Click or drag a close-up face photo") : measurementType === "head" ? t2("Click or drag a head-and-shoulders photo") : t2("Click or drag a full-body photo"),
|
|
22314
|
+
onPhotoAccepted: (f2) => setGuideFile(f2),
|
|
22315
|
+
onClearPhoto: () => setGuideFile(null),
|
|
22316
|
+
t: t2
|
|
22111
22317
|
}
|
|
22112
|
-
),
|
|
22318
|
+
) }),
|
|
22113
22319
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { style: { flex: 1, display: "flex", flexDirection: "column", justifyContent: "center", gap: "0.6vw" }, children: [
|
|
22114
22320
|
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { style: { fontSize: "0.85vw", fontWeight: 700, color: "var(--ps-text-primary)", marginBottom: "0.3vw" }, children: t2("How to take the best photo") }),
|
|
22115
22321
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { style: { background: "#ddfbe7", borderRadius: "0.5vw", padding: "0.6vw 0.8vw" }, children: [
|
|
@@ -23183,6 +23389,30 @@ function CreateProfileWizard({ onSave, onCancel, apiUrl, apiKey, onPhotoPreview,
|
|
|
23183
23389
|
if (!photoBase64 && ageConfirmed === true) setUploadHoverCpw(true);
|
|
23184
23390
|
},
|
|
23185
23391
|
onMouseLeave: () => setUploadHoverCpw(false),
|
|
23392
|
+
onDragEnter: (e) => {
|
|
23393
|
+
e.preventDefault();
|
|
23394
|
+
e.stopPropagation();
|
|
23395
|
+
},
|
|
23396
|
+
onDragOver: (e) => {
|
|
23397
|
+
e.preventDefault();
|
|
23398
|
+
e.stopPropagation();
|
|
23399
|
+
if (e.dataTransfer) e.dataTransfer.dropEffect = "copy";
|
|
23400
|
+
},
|
|
23401
|
+
onDragLeave: (e) => {
|
|
23402
|
+
e.preventDefault();
|
|
23403
|
+
e.stopPropagation();
|
|
23404
|
+
},
|
|
23405
|
+
onDrop: (e) => {
|
|
23406
|
+
e.preventDefault();
|
|
23407
|
+
e.stopPropagation();
|
|
23408
|
+
if (photoBase64 || ageConfirmed !== true) return;
|
|
23409
|
+
const file = e.dataTransfer?.files?.[0];
|
|
23410
|
+
if (!file || !photoInputRef.current) return;
|
|
23411
|
+
const dt = new DataTransfer();
|
|
23412
|
+
dt.items.add(file);
|
|
23413
|
+
photoInputRef.current.files = dt.files;
|
|
23414
|
+
photoInputRef.current.dispatchEvent(new Event("change", { bubbles: true }));
|
|
23415
|
+
},
|
|
23186
23416
|
style: {
|
|
23187
23417
|
flex: 1,
|
|
23188
23418
|
display: "flex",
|
|
@@ -25666,6 +25896,30 @@ function BodyProfileView({
|
|
|
25666
25896
|
if (!photoPreview && ageConfirmed === true) setUploadHover(true);
|
|
25667
25897
|
},
|
|
25668
25898
|
onMouseLeave: () => setUploadHover(false),
|
|
25899
|
+
onDragEnter: (e) => {
|
|
25900
|
+
e.preventDefault();
|
|
25901
|
+
e.stopPropagation();
|
|
25902
|
+
},
|
|
25903
|
+
onDragOver: (e) => {
|
|
25904
|
+
e.preventDefault();
|
|
25905
|
+
e.stopPropagation();
|
|
25906
|
+
if (e.dataTransfer) e.dataTransfer.dropEffect = "copy";
|
|
25907
|
+
},
|
|
25908
|
+
onDragLeave: (e) => {
|
|
25909
|
+
e.preventDefault();
|
|
25910
|
+
e.stopPropagation();
|
|
25911
|
+
},
|
|
25912
|
+
onDrop: (e) => {
|
|
25913
|
+
e.preventDefault();
|
|
25914
|
+
e.stopPropagation();
|
|
25915
|
+
if (photoPreview || ageConfirmed !== true) return;
|
|
25916
|
+
const file = e.dataTransfer?.files?.[0];
|
|
25917
|
+
if (!file || !fileInputRef.current) return;
|
|
25918
|
+
const dt = new DataTransfer();
|
|
25919
|
+
dt.items.add(file);
|
|
25920
|
+
fileInputRef.current.files = dt.files;
|
|
25921
|
+
fileInputRef.current.dispatchEvent(new Event("change", { bubbles: true }));
|
|
25922
|
+
},
|
|
25669
25923
|
style: {
|
|
25670
25924
|
flex: 1,
|
|
25671
25925
|
display: "flex",
|
|
@@ -25959,7 +26213,7 @@ function BodyProfileView({
|
|
|
25959
26213
|
] }),
|
|
25960
26214
|
/* @__PURE__ */ jsxRuntimeExports.jsx("p", { style: { margin: "0.2vw 0 0", fontSize: "0.65vw", color: "var(--ps-text-muted)" }, children: t2("These calibrate the AI — all required.") })
|
|
25961
26215
|
] }),
|
|
25962
|
-
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-bp-system-toggle", style: { alignSelf: "center" }, children: [
|
|
26216
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-bp-system-toggle", style: { alignSelf: "center", marginTop: "1.2vw" }, children: [
|
|
25963
26217
|
/* @__PURE__ */ jsxRuntimeExports.jsx("button", { className: `ps-bp-system-btn${!isImperialMode ? " ps-bp-system-active" : ""}`, onClick: photoSwitchToMetric, type: "button", children: t2("Metric") }),
|
|
25964
26218
|
/* @__PURE__ */ jsxRuntimeExports.jsx("button", { className: `ps-bp-system-btn${isImperialMode ? " ps-bp-system-active" : ""}`, onClick: photoSwitchToImperial, type: "button", children: t2("Imperial") })
|
|
25965
26219
|
] }),
|
|
@@ -29480,6 +29734,8 @@ function PrimeStyleTryonInner({
|
|
|
29480
29734
|
PhotoGuideView,
|
|
29481
29735
|
{
|
|
29482
29736
|
measurementType: detectMeasurementType(productTitle),
|
|
29737
|
+
apiUrl: getApiUrl(apiUrl),
|
|
29738
|
+
apiKey: getApiKey(),
|
|
29483
29739
|
onBack: () => setView("no-chart"),
|
|
29484
29740
|
onSubmit: (file) => {
|
|
29485
29741
|
handleFileSelect(file);
|