@primestyleai/tryon 5.10.100 → 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.
@@ -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: [
@@ -21928,8 +22032,191 @@ 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
+ }
21931
22216
  function PhotoGuideView({
21932
22217
  measurementType = "body",
22218
+ apiUrl,
22219
+ apiKey,
21933
22220
  onBack,
21934
22221
  onSubmit,
21935
22222
  t: t2
@@ -21937,8 +22224,6 @@ function PhotoGuideView({
21937
22224
  const isMobile = useIsMobile();
21938
22225
  const [guideFile, setGuideFile] = reactExports.useState(null);
21939
22226
  const [guidePreviewUrl, setGuidePreviewUrl] = reactExports.useState(null);
21940
- const [, setGuideDragOver] = reactExports.useState(false);
21941
- const guideInputRef = reactExports.useRef(null);
21942
22227
  reactExports.useEffect(() => {
21943
22228
  if (!guideFile) {
21944
22229
  setGuidePreviewUrl(null);
@@ -21958,49 +22243,17 @@ function PhotoGuideView({
21958
22243
  /* @__PURE__ */ jsxRuntimeExports.jsx("h2", { className: "ps-pm-title", children: t2("Review your photo") }),
21959
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.") })
21960
22245
  ] }),
21961
- /* @__PURE__ */ jsxRuntimeExports.jsx(
21962
- "input",
22246
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-pm-preview", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
22247
+ PhotoUploadZone,
21963
22248
  {
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",
21991
- {
21992
- type: "button",
21993
- className: "ps-pm-preview-empty",
21994
- onClick: () => guideInputRef.current?.click(),
21995
- children: [
21996
- /* @__PURE__ */ jsxRuntimeExports.jsxs("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round", width: "32", height: "32", children: [
21997
- /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" }),
21998
- /* @__PURE__ */ jsxRuntimeExports.jsx("polyline", { points: "17 8 12 3 7 8" }),
21999
- /* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: "12", y1: "3", x2: "12", y2: "15" })
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
- ]
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
22004
22257
  }
22005
22258
  ) }),
22006
22259
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-pm-checklist", children: [
@@ -22039,7 +22292,7 @@ function PhotoGuideView({
22039
22292
  {
22040
22293
  type: "button",
22041
22294
  className: "ps-pm-secondary-btn",
22042
- onClick: () => guideInputRef.current?.click(),
22295
+ onClick: () => setGuideFile(null),
22043
22296
  children: t2("RETAKE PHOTO")
22044
22297
  }
22045
22298
  ) : /* @__PURE__ */ jsxRuntimeExports.jsx(
@@ -22056,60 +22309,20 @@ function PhotoGuideView({
22056
22309
  }
22057
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: [
22058
22311
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { style: { display: "flex", gap: "1.2vw", flex: 1, alignItems: "stretch", minHeight: 0, overflow: "hidden" }, children: [
22059
- /* @__PURE__ */ jsxRuntimeExports.jsxs(
22060
- "div",
22312
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { style: { flex: 1, display: "flex", minHeight: 0 }, children: /* @__PURE__ */ jsxRuntimeExports.jsx(
22313
+ PhotoUploadZone,
22061
22314
  {
22062
- onClick: () => guideInputRef.current?.click(),
22063
- onDragOver: (e) => {
22064
- e.preventDefault();
22065
- setGuideDragOver(true);
22066
- },
22067
- onDragLeave: () => setGuideDragOver(false),
22068
- onDrop: (e) => {
22069
- e.preventDefault();
22070
- setGuideDragOver(false);
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
- ]
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
22111
22324
  }
22112
- ),
22325
+ ) }),
22113
22326
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { style: { flex: 1, display: "flex", flexDirection: "column", justifyContent: "center", gap: "0.6vw" }, children: [
22114
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") }),
22115
22328
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { style: { background: "#ddfbe7", borderRadius: "0.5vw", padding: "0.6vw 0.8vw" }, children: [
@@ -23183,6 +23396,30 @@ function CreateProfileWizard({ onSave, onCancel, apiUrl, apiKey, onPhotoPreview,
23183
23396
  if (!photoBase64 && ageConfirmed === true) setUploadHoverCpw(true);
23184
23397
  },
23185
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
+ },
23186
23423
  style: {
23187
23424
  flex: 1,
23188
23425
  display: "flex",
@@ -25666,6 +25903,30 @@ function BodyProfileView({
25666
25903
  if (!photoPreview && ageConfirmed === true) setUploadHover(true);
25667
25904
  },
25668
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
+ },
25669
25930
  style: {
25670
25931
  flex: 1,
25671
25932
  display: "flex",
@@ -25959,7 +26220,7 @@ function BodyProfileView({
25959
26220
  ] }),
25960
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.") })
25961
26222
  ] }),
25962
- /* @__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: [
25963
26224
  /* @__PURE__ */ jsxRuntimeExports.jsx("button", { className: `ps-bp-system-btn${!isImperialMode ? " ps-bp-system-active" : ""}`, onClick: photoSwitchToMetric, type: "button", children: t2("Metric") }),
25964
26225
  /* @__PURE__ */ jsxRuntimeExports.jsx("button", { className: `ps-bp-system-btn${isImperialMode ? " ps-bp-system-active" : ""}`, onClick: photoSwitchToImperial, type: "button", children: t2("Imperial") })
25965
26226
  ] }),
@@ -29480,6 +29741,8 @@ function PrimeStyleTryonInner({
29480
29741
  PhotoGuideView,
29481
29742
  {
29482
29743
  measurementType: detectMeasurementType(productTitle),
29744
+ apiUrl: getApiUrl(apiUrl),
29745
+ apiKey: getApiKey(),
29483
29746
  onBack: () => setView("no-chart"),
29484
29747
  onSubmit: (file) => {
29485
29748
  handleFileSelect(file);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@primestyleai/tryon",
3
- "version": "5.10.100",
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",