@primestyleai/tryon 5.10.99 → 5.10.101

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.
@@ -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: [
@@ -21928,6 +22032,384 @@ function UploadView({
21928
22032
  }
21929
22033
  ) });
21930
22034
  }
22035
+ function PhotoUploadZone({
22036
+ apiUrl,
22037
+ apiKey,
22038
+ previewUrl,
22039
+ requireAgeConfirm = false,
22040
+ ageConfirmed,
22041
+ disabled = false,
22042
+ hintLine,
22043
+ emptyTitle,
22044
+ variant = "block",
22045
+ onPhotoAccepted,
22046
+ onClearPhoto,
22047
+ t: t2
22048
+ }) {
22049
+ const inputRef = reactExports.useRef(null);
22050
+ const dragDepthRef = reactExports.useRef(0);
22051
+ const [dragOver, setDragOver] = reactExports.useState(false);
22052
+ const [processing, setProcessing] = reactExports.useState(false);
22053
+ const [statusText, setStatusText] = reactExports.useState("");
22054
+ const [rejection, setRejection] = reactExports.useState(null);
22055
+ const [localError, setLocalError] = reactExports.useState(null);
22056
+ reactExports.useEffect(() => {
22057
+ if (!previewUrl) {
22058
+ setRejection(null);
22059
+ setLocalError(null);
22060
+ }
22061
+ }, [previewUrl]);
22062
+ const acceptFile = async (file) => {
22063
+ if (disabled || processing) return;
22064
+ setLocalError(null);
22065
+ setRejection(null);
22066
+ if (requireAgeConfirm && ageConfirmed !== true) {
22067
+ setLocalError(t2("Please confirm that the person in the photo is 18 or older before uploading."));
22068
+ return;
22069
+ }
22070
+ if (!file.type.startsWith("image/")) {
22071
+ setLocalError(t2("Please upload an image file"));
22072
+ return;
22073
+ }
22074
+ if (file.size > 10 * 1024 * 1024) {
22075
+ setLocalError(t2("Image must be under 10MB"));
22076
+ return;
22077
+ }
22078
+ if (apiUrl && apiKey) {
22079
+ setProcessing(true);
22080
+ setStatusText(t2("Analyzing photo…"));
22081
+ try {
22082
+ const ageResult = await checkAgeBeforeUpload(file, apiUrl, apiKey);
22083
+ if (!ageResult.isAdult) {
22084
+ const reason = ageResult.reasoning?.trim() || t2("This photo appears to be of a minor. Please upload a photo of someone 18 or older.");
22085
+ setRejection(reason);
22086
+ setProcessing(false);
22087
+ setStatusText("");
22088
+ return;
22089
+ }
22090
+ try {
22091
+ await compressImage(file, { maxDimension: 1024, quality: 0.85 });
22092
+ } catch {
22093
+ }
22094
+ } catch {
22095
+ } finally {
22096
+ setProcessing(false);
22097
+ setStatusText("");
22098
+ }
22099
+ }
22100
+ onPhotoAccepted(file);
22101
+ };
22102
+ const onClick = () => {
22103
+ if (disabled || processing) return;
22104
+ inputRef.current?.click();
22105
+ };
22106
+ const onDragEnter = (e) => {
22107
+ e.preventDefault();
22108
+ e.stopPropagation();
22109
+ if (disabled || processing) return;
22110
+ dragDepthRef.current += 1;
22111
+ setDragOver(true);
22112
+ };
22113
+ const onDragOver = (e) => {
22114
+ e.preventDefault();
22115
+ e.stopPropagation();
22116
+ if (e.dataTransfer) e.dataTransfer.dropEffect = "copy";
22117
+ };
22118
+ const onDragLeave = (e) => {
22119
+ e.preventDefault();
22120
+ e.stopPropagation();
22121
+ dragDepthRef.current = Math.max(0, dragDepthRef.current - 1);
22122
+ if (dragDepthRef.current === 0) setDragOver(false);
22123
+ };
22124
+ const onDrop = (e) => {
22125
+ e.preventDefault();
22126
+ e.stopPropagation();
22127
+ dragDepthRef.current = 0;
22128
+ setDragOver(false);
22129
+ if (disabled || processing) return;
22130
+ const file = e.dataTransfer?.files?.[0];
22131
+ if (file) void acceptFile(file);
22132
+ };
22133
+ const onChange = (e) => {
22134
+ const file = e.target.files?.[0];
22135
+ if (file) void acceptFile(file);
22136
+ if (inputRef.current) inputRef.current.value = "";
22137
+ };
22138
+ const removePhoto = () => {
22139
+ setRejection(null);
22140
+ setLocalError(null);
22141
+ onClearPhoto?.();
22142
+ };
22143
+ const isInline = variant === "inline";
22144
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs(
22145
+ "div",
22146
+ {
22147
+ className: `ps-photo-zone${dragOver ? " ps-photo-zone-drag" : ""}${isInline ? " ps-photo-zone-inline" : ""}${previewUrl ? " ps-photo-zone-has" : ""}`,
22148
+ onClick: previewUrl ? void 0 : onClick,
22149
+ onDragEnter,
22150
+ onDragOver,
22151
+ onDragLeave,
22152
+ onDrop,
22153
+ children: [
22154
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
22155
+ "input",
22156
+ {
22157
+ ref: inputRef,
22158
+ type: "file",
22159
+ accept: "image/jpeg,image/png,image/webp",
22160
+ style: { display: "none" },
22161
+ onChange
22162
+ }
22163
+ ),
22164
+ previewUrl && !rejection && /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
22165
+ /* @__PURE__ */ jsxRuntimeExports.jsx("img", { src: previewUrl, alt: t2("Your photo"), className: "ps-photo-zone-img" }),
22166
+ /* @__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") }) }),
22167
+ onClearPhoto && /* @__PURE__ */ jsxRuntimeExports.jsx(
22168
+ "button",
22169
+ {
22170
+ type: "button",
22171
+ className: "ps-photo-zone-remove",
22172
+ onClick: (e) => {
22173
+ e.stopPropagation();
22174
+ removePhoto();
22175
+ },
22176
+ "aria-label": t2("Remove photo"),
22177
+ 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: [
22178
+ /* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
22179
+ /* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: "6", y1: "6", x2: "18", y2: "18" })
22180
+ ] })
22181
+ }
22182
+ )
22183
+ ] }),
22184
+ !previewUrl && !rejection && !processing && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-photo-zone-empty", children: [
22185
+ /* @__PURE__ */ jsxRuntimeExports.jsx(UploadIcon, { size: 32 }),
22186
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "ps-photo-zone-title", children: emptyTitle || t2("Upload your photo") }),
22187
+ hintLine && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "ps-photo-zone-hint", children: hintLine }),
22188
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "ps-photo-zone-hint", children: t2("JPEG, PNG up to 10MB · click or drop") })
22189
+ ] }),
22190
+ processing && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-photo-zone-processing", children: [
22191
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-photo-zone-spinner" }),
22192
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "ps-photo-zone-status", children: statusText })
22193
+ ] }),
22194
+ rejection && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-photo-zone-rejection", onClick: (e) => e.stopPropagation(), children: [
22195
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-photo-zone-rejection-icon", "aria-hidden": "true", children: "!" }),
22196
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-photo-zone-rejection-title", children: t2("Different photo needed") }),
22197
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-photo-zone-rejection-msg", children: rejection }),
22198
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
22199
+ "button",
22200
+ {
22201
+ type: "button",
22202
+ className: "ps-photo-zone-rejection-cta",
22203
+ onClick: () => {
22204
+ setRejection(null);
22205
+ inputRef.current?.click();
22206
+ },
22207
+ children: t2("Choose another photo")
22208
+ }
22209
+ )
22210
+ ] }),
22211
+ localError && !rejection && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-photo-zone-error", onClick: (e) => e.stopPropagation(), children: localError })
22212
+ ]
22213
+ }
22214
+ );
22215
+ }
22216
+ function PhotoGuideView({
22217
+ measurementType = "body",
22218
+ apiUrl,
22219
+ apiKey,
22220
+ onBack,
22221
+ onSubmit,
22222
+ t: t2
22223
+ }) {
22224
+ const isMobile = useIsMobile();
22225
+ const [guideFile, setGuideFile] = reactExports.useState(null);
22226
+ const [guidePreviewUrl, setGuidePreviewUrl] = reactExports.useState(null);
22227
+ reactExports.useEffect(() => {
22228
+ if (!guideFile) {
22229
+ setGuidePreviewUrl(null);
22230
+ return;
22231
+ }
22232
+ const url = URL.createObjectURL(guideFile);
22233
+ setGuidePreviewUrl(url);
22234
+ return () => URL.revokeObjectURL(url);
22235
+ }, [guideFile]);
22236
+ const submit = () => {
22237
+ if (!guideFile) return;
22238
+ onSubmit(guideFile);
22239
+ };
22240
+ if (isMobile) {
22241
+ return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-bp-wrapper", style: { padding: "16px 16px 0", background: "var(--ps-bg-primary)" }, children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-pm-root", children: [
22242
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-pm-header", children: [
22243
+ /* @__PURE__ */ jsxRuntimeExports.jsx("h2", { className: "ps-pm-title", children: t2("Review your photo") }),
22244
+ /* @__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.") })
22245
+ ] }),
22246
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-pm-preview", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
22247
+ PhotoUploadZone,
22248
+ {
22249
+ apiUrl,
22250
+ apiKey,
22251
+ previewUrl: guidePreviewUrl,
22252
+ variant: "inline",
22253
+ onPhotoAccepted: (f2) => setGuideFile(f2),
22254
+ onClearPhoto: () => setGuideFile(null),
22255
+ emptyTitle: t2("Tap to upload"),
22256
+ t: t2
22257
+ }
22258
+ ) }),
22259
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-pm-checklist", children: [
22260
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-pm-checklist-icon", children: /* @__PURE__ */ jsxRuntimeExports.jsx("svg", { viewBox: "0 0 24 24", fill: "currentColor", width: "14", height: "14", children: /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z" }) }) }),
22261
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-pm-checklist-body", children: [
22262
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-pm-checklist-title", children: t2("Checklist for accuracy") }),
22263
+ /* @__PURE__ */ jsxRuntimeExports.jsx("ul", { className: "ps-pm-checklist-items", children: measurementType === "face" ? /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
22264
+ /* @__PURE__ */ jsxRuntimeExports.jsx("li", { children: t2("Face the camera directly at eye level") }),
22265
+ /* @__PURE__ */ jsxRuntimeExports.jsx("li", { children: t2("Remove any glasses you're wearing") }),
22266
+ /* @__PURE__ */ jsxRuntimeExports.jsx("li", { children: t2("Good lighting, plain background") })
22267
+ ] }) : measurementType === "head" ? /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
22268
+ /* @__PURE__ */ jsxRuntimeExports.jsx("li", { children: t2("Head and shoulders in frame") }),
22269
+ /* @__PURE__ */ jsxRuntimeExports.jsx("li", { children: t2("Leave space above your head") }),
22270
+ /* @__PURE__ */ jsxRuntimeExports.jsx("li", { children: t2("Good lighting, plain background") })
22271
+ ] }) : /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
22272
+ /* @__PURE__ */ jsxRuntimeExports.jsx("li", { children: t2("Form-fitting clothing is recommended") }),
22273
+ /* @__PURE__ */ jsxRuntimeExports.jsx("li", { children: t2("Standing 2-3 meters from camera") }),
22274
+ /* @__PURE__ */ jsxRuntimeExports.jsx("li", { children: t2("Neutral background with good lighting") })
22275
+ ] }) })
22276
+ ] })
22277
+ ] }),
22278
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-bpm-spacer" }),
22279
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-bpm-bottom", children: [
22280
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
22281
+ "button",
22282
+ {
22283
+ type: "button",
22284
+ className: "ps-pm-primary-btn",
22285
+ disabled: !guideFile,
22286
+ onClick: submit,
22287
+ children: t2("START TRY-ON")
22288
+ }
22289
+ ),
22290
+ guideFile ? /* @__PURE__ */ jsxRuntimeExports.jsx(
22291
+ "button",
22292
+ {
22293
+ type: "button",
22294
+ className: "ps-pm-secondary-btn",
22295
+ onClick: () => setGuideFile(null),
22296
+ children: t2("RETAKE PHOTO")
22297
+ }
22298
+ ) : /* @__PURE__ */ jsxRuntimeExports.jsx(
22299
+ "button",
22300
+ {
22301
+ type: "button",
22302
+ className: "ps-pm-secondary-btn",
22303
+ onClick: onBack,
22304
+ children: t2("BACK")
22305
+ }
22306
+ )
22307
+ ] })
22308
+ ] }) });
22309
+ }
22310
+ 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: [
22311
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { style: { display: "flex", gap: "1.2vw", flex: 1, alignItems: "stretch", minHeight: 0, overflow: "hidden" }, children: [
22312
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { style: { flex: 1, display: "flex", minHeight: 0 }, children: /* @__PURE__ */ jsxRuntimeExports.jsx(
22313
+ PhotoUploadZone,
22314
+ {
22315
+ apiUrl,
22316
+ apiKey,
22317
+ previewUrl: guidePreviewUrl,
22318
+ variant: "inline",
22319
+ emptyTitle: t2("Upload your photo"),
22320
+ 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"),
22321
+ onPhotoAccepted: (f2) => setGuideFile(f2),
22322
+ onClearPhoto: () => setGuideFile(null),
22323
+ t: t2
22324
+ }
22325
+ ) }),
22326
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { style: { flex: 1, display: "flex", flexDirection: "column", justifyContent: "center", gap: "0.6vw" }, children: [
22327
+ /* @__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") }),
22328
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { style: { background: "#ddfbe7", borderRadius: "0.5vw", padding: "0.6vw 0.8vw" }, children: [
22329
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { style: { display: "flex", alignItems: "center", gap: "0.3vw", marginBottom: "0.3vw" }, children: [
22330
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { style: { color: "#1c9d4c", fontSize: "0.75vw", fontWeight: 700 }, children: "✓" }),
22331
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { style: { color: "#1c9d4c", fontSize: "0.65vw", fontWeight: 600 }, children: t2("Do") })
22332
+ ] }),
22333
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { style: { fontSize: "0.58vw", color: "var(--ps-text-primary)", lineHeight: 1.8 }, children: measurementType === "face" ? /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
22334
+ t2("Face the camera directly, centered in frame"),
22335
+ /* @__PURE__ */ jsxRuntimeExports.jsx("br", {}),
22336
+ t2("Use natural, even lighting (e.g. near a window)"),
22337
+ /* @__PURE__ */ jsxRuntimeExports.jsx("br", {}),
22338
+ t2("Keep hair away from your face and ears"),
22339
+ /* @__PURE__ */ jsxRuntimeExports.jsx("br", {}),
22340
+ t2("Choose a plain, light background")
22341
+ ] }) : measurementType === "head" ? /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
22342
+ t2("Face the camera with head and shoulders in frame"),
22343
+ /* @__PURE__ */ jsxRuntimeExports.jsx("br", {}),
22344
+ t2("Leave some space above your head"),
22345
+ /* @__PURE__ */ jsxRuntimeExports.jsx("br", {}),
22346
+ t2("Use natural, even lighting"),
22347
+ /* @__PURE__ */ jsxRuntimeExports.jsx("br", {}),
22348
+ t2("Choose a plain, light background")
22349
+ ] }) : /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
22350
+ t2("Stand facing the camera with your full body in frame"),
22351
+ /* @__PURE__ */ jsxRuntimeExports.jsx("br", {}),
22352
+ t2("Use natural or even lighting"),
22353
+ /* @__PURE__ */ jsxRuntimeExports.jsx("br", {}),
22354
+ t2("Wear fitted, simple clothing"),
22355
+ /* @__PURE__ */ jsxRuntimeExports.jsx("br", {}),
22356
+ t2("Stand straight and still, arms relaxed")
22357
+ ] }) })
22358
+ ] }),
22359
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { style: { background: "#ffe2e2", borderRadius: "0.5vw", padding: "0.6vw 0.8vw" }, children: [
22360
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { style: { display: "flex", alignItems: "center", gap: "0.3vw", marginBottom: "0.3vw" }, children: [
22361
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { style: { color: "#e7000b", fontSize: "0.75vw", fontWeight: 700 }, children: "✗" }),
22362
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { style: { color: "#e7000b", fontSize: "0.65vw", fontWeight: 600 }, children: t2("Don't") })
22363
+ ] }),
22364
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { style: { fontSize: "0.58vw", color: "var(--ps-text-primary)", lineHeight: 1.8 }, children: measurementType === "face" ? /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
22365
+ t2("Don't wear sunglasses or a hat in the photo"),
22366
+ /* @__PURE__ */ jsxRuntimeExports.jsx("br", {}),
22367
+ t2("Don't tilt or turn your head"),
22368
+ /* @__PURE__ */ jsxRuntimeExports.jsx("br", {}),
22369
+ t2("Don't use strong backlighting or flash"),
22370
+ /* @__PURE__ */ jsxRuntimeExports.jsx("br", {}),
22371
+ t2("Don't apply filters or edits")
22372
+ ] }) : measurementType === "head" ? /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
22373
+ t2("Don't wear a hat in the photo"),
22374
+ /* @__PURE__ */ jsxRuntimeExports.jsx("br", {}),
22375
+ t2("Don't crop out the top of your head"),
22376
+ /* @__PURE__ */ jsxRuntimeExports.jsx("br", {}),
22377
+ t2("Don't use strong backlighting or flash"),
22378
+ /* @__PURE__ */ jsxRuntimeExports.jsx("br", {}),
22379
+ t2("Don't apply filters or edits")
22380
+ ] }) : /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
22381
+ t2("Don't wear loose or baggy clothing"),
22382
+ /* @__PURE__ */ jsxRuntimeExports.jsx("br", {}),
22383
+ t2("Don't sit, pose, or bend"),
22384
+ /* @__PURE__ */ jsxRuntimeExports.jsx("br", {}),
22385
+ t2("Don't take mirror photos or selfies"),
22386
+ /* @__PURE__ */ jsxRuntimeExports.jsx("br", {}),
22387
+ t2("Don't apply filters or edits")
22388
+ ] }) })
22389
+ ] }),
22390
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { style: { background: "rgba(59,130,246,0.08)", border: "1px solid rgba(59,130,246,0.2)", borderRadius: "0.5vw", padding: "0.5vw 0.8vw" }, children: [
22391
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { style: { display: "flex", alignItems: "center", gap: "0.3vw", marginBottom: "0.2vw" }, children: [
22392
+ /* @__PURE__ */ jsxRuntimeExports.jsx(SparkleIcon, { size: 12 }),
22393
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { style: { color: "var(--ps-accent)", fontSize: "0.65vw", fontWeight: 700 }, children: t2("Pro Tip") })
22394
+ ] }),
22395
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { style: { fontSize: "0.55vw", color: "var(--ps-text-secondary)", lineHeight: 1.7 }, children: measurementType === "face" ? t2("A clear, well-lit face photo gives the most accurate eyewear try-on.") : measurementType === "head" ? t2("A clear head-and-shoulders photo with space above your head gives the most accurate headwear try-on.") : t2("Our AI works best with front-facing, full-body photos in fitted clothing. Better photos = more accurate virtual try-on!") })
22396
+ ] })
22397
+ ] })
22398
+ ] }),
22399
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { style: { display: "flex", alignItems: "center", justifyContent: "space-between", marginTop: "0.8vw", gap: "0.5vw", flexShrink: 0 }, children: [
22400
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("button", { className: "ps-bp-back-btn", onClick: onBack, type: "button", children: [
22401
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "ps-bp-back-arrow", children: "←" }),
22402
+ " ",
22403
+ t2("Back")
22404
+ ] }),
22405
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("button", { className: "ps-tryon-v2-cta", style: { marginTop: 0 }, disabled: !guideFile, onClick: submit, children: [
22406
+ /* @__PURE__ */ jsxRuntimeExports.jsx(CameraIcon$1, { size: 14 }),
22407
+ " ",
22408
+ t2("Start Try-On")
22409
+ ] })
22410
+ ] })
22411
+ ] });
22412
+ }
21931
22413
  const RING_RADIUS = 38;
21932
22414
  const RING_CIRCUMFERENCE = 2 * Math.PI * RING_RADIUS;
21933
22415
  function ProcessingView({
@@ -22914,6 +23396,30 @@ function CreateProfileWizard({ onSave, onCancel, apiUrl, apiKey, onPhotoPreview,
22914
23396
  if (!photoBase64 && ageConfirmed === true) setUploadHoverCpw(true);
22915
23397
  },
22916
23398
  onMouseLeave: () => setUploadHoverCpw(false),
23399
+ onDragEnter: (e) => {
23400
+ e.preventDefault();
23401
+ e.stopPropagation();
23402
+ },
23403
+ onDragOver: (e) => {
23404
+ e.preventDefault();
23405
+ e.stopPropagation();
23406
+ if (e.dataTransfer) e.dataTransfer.dropEffect = "copy";
23407
+ },
23408
+ onDragLeave: (e) => {
23409
+ e.preventDefault();
23410
+ e.stopPropagation();
23411
+ },
23412
+ onDrop: (e) => {
23413
+ e.preventDefault();
23414
+ e.stopPropagation();
23415
+ if (photoBase64 || ageConfirmed !== true) return;
23416
+ const file = e.dataTransfer?.files?.[0];
23417
+ if (!file || !photoInputRef.current) return;
23418
+ const dt = new DataTransfer();
23419
+ dt.items.add(file);
23420
+ photoInputRef.current.files = dt.files;
23421
+ photoInputRef.current.dispatchEvent(new Event("change", { bubbles: true }));
23422
+ },
22917
23423
  style: {
22918
23424
  flex: 1,
22919
23425
  display: "flex",
@@ -25397,6 +25903,30 @@ function BodyProfileView({
25397
25903
  if (!photoPreview && ageConfirmed === true) setUploadHover(true);
25398
25904
  },
25399
25905
  onMouseLeave: () => setUploadHover(false),
25906
+ onDragEnter: (e) => {
25907
+ e.preventDefault();
25908
+ e.stopPropagation();
25909
+ },
25910
+ onDragOver: (e) => {
25911
+ e.preventDefault();
25912
+ e.stopPropagation();
25913
+ if (e.dataTransfer) e.dataTransfer.dropEffect = "copy";
25914
+ },
25915
+ onDragLeave: (e) => {
25916
+ e.preventDefault();
25917
+ e.stopPropagation();
25918
+ },
25919
+ onDrop: (e) => {
25920
+ e.preventDefault();
25921
+ e.stopPropagation();
25922
+ if (photoPreview || ageConfirmed !== true) return;
25923
+ const file = e.dataTransfer?.files?.[0];
25924
+ if (!file || !fileInputRef.current) return;
25925
+ const dt = new DataTransfer();
25926
+ dt.items.add(file);
25927
+ fileInputRef.current.files = dt.files;
25928
+ fileInputRef.current.dispatchEvent(new Event("change", { bubbles: true }));
25929
+ },
25400
25930
  style: {
25401
25931
  flex: 1,
25402
25932
  display: "flex",
@@ -25690,7 +26220,7 @@ function BodyProfileView({
25690
26220
  ] }),
25691
26221
  /* @__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.") })
25692
26222
  ] }),
25693
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-bp-system-toggle", style: { alignSelf: "center" }, children: [
26223
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-bp-system-toggle", style: { alignSelf: "center", marginTop: "1.2vw" }, children: [
25694
26224
  /* @__PURE__ */ jsxRuntimeExports.jsx("button", { className: `ps-bp-system-btn${!isImperialMode ? " ps-bp-system-active" : ""}`, onClick: photoSwitchToMetric, type: "button", children: t2("Metric") }),
25695
26225
  /* @__PURE__ */ jsxRuntimeExports.jsx("button", { className: `ps-bp-system-btn${isImperialMode ? " ps-bp-system-active" : ""}`, onClick: photoSwitchToImperial, type: "button", children: t2("Imperial") })
25696
26226
  ] }),
@@ -29200,12 +29730,27 @@ function PrimeStyleTryonInner({
29200
29730
  {
29201
29731
  productImage,
29202
29732
  productTitle,
29203
- onTryOn: () => setView("body-profile"),
29733
+ onTryOn: () => setView("photo-guide"),
29204
29734
  onClose: onClose ?? (() => {
29205
29735
  }),
29206
29736
  t: t2
29207
29737
  }
29208
29738
  ) }, "v-nochart");
29739
+ case "photo-guide":
29740
+ return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-tryon-view-enter", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
29741
+ PhotoGuideView,
29742
+ {
29743
+ measurementType: detectMeasurementType(productTitle),
29744
+ apiUrl: getApiUrl(apiUrl),
29745
+ apiKey: getApiKey(),
29746
+ onBack: () => setView("no-chart"),
29747
+ onSubmit: (file) => {
29748
+ handleFileSelect(file);
29749
+ handleTryOnSubmit(file);
29750
+ },
29751
+ t: t2
29752
+ }
29753
+ ) }, "v-photoguide");
29209
29754
  default:
29210
29755
  return null;
29211
29756
  }
@@ -29227,7 +29772,7 @@ function PrimeStyleTryonInner({
29227
29772
  }
29228
29773
  ),
29229
29774
  view !== "idle" && typeof document !== "undefined" && reactDomExports.createPortal(
29230
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: cx("ps-tryon-overlay", cn.overlay), style: cssVars, "data-ps-tryon-portal": true, children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: cx(`ps-tryon-modal${view === "result" && resultImageUrl && sizingResult || view === "size-result" || view === "estimation-review" || view === "body-profile" || view === "profiles" || view === "no-chart" ? " ps-tryon-modal-wide" : ""}`, cn.modal), onClick: (e) => e.stopPropagation(), children: [
29775
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: cx("ps-tryon-overlay", cn.overlay), style: cssVars, "data-ps-tryon-portal": true, children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: cx(`ps-tryon-modal${view === "result" && resultImageUrl && sizingResult || view === "size-result" || view === "estimation-review" || view === "body-profile" || view === "profiles" || view === "no-chart" || view === "photo-guide" ? " ps-tryon-modal-wide" : ""}`, cn.modal), onClick: (e) => e.stopPropagation(), children: [
29231
29776
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: cx("ps-tryon-header ps-tryon-header-minimal", cn.header), children: [
29232
29777
  /* @__PURE__ */ jsxRuntimeExports.jsx(LangSwitcher, { activeLocale, onSelect: setActiveLocale }),
29233
29778
  /* @__PURE__ */ jsxRuntimeExports.jsx("button", { className: "ps-tryon-header-icon", title: t2("Profiles"), onClick: () => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@primestyleai/tryon",
3
- "version": "5.10.99",
3
+ "version": "5.10.101",
4
4
  "description": "PrimeStyle Virtual Try-On SDK — React component & Web Component",
5
5
  "type": "module",
6
6
  "main": "dist/primestyle-tryon.js",