@primestyleai/tryon 2.0.0 → 2.0.1

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.
@@ -1,96 +1,136 @@
1
1
  "use client";
2
2
  import { jsxs, jsx, Fragment } from "react/jsx-runtime";
3
- import { useState, useRef, useEffect, useCallback } from "react";
3
+ import { useState, useRef, useEffect, useMemo, useCallback } from "react";
4
4
  import { A as ApiClient, S as SseClient, i as isValidImageFile, c as compressImage, P as PrimeStyleError } from "../image-utils-C9bJ1zKO.js";
5
5
  function cx(base, override) {
6
6
  return override ? `${base} ${override}` : base;
7
7
  }
8
+ const LS_PREFIX = "primestyle_";
9
+ function lsGet(key, fallback) {
10
+ try {
11
+ const raw = localStorage.getItem(LS_PREFIX + key);
12
+ return raw ? JSON.parse(raw) : fallback;
13
+ } catch {
14
+ return fallback;
15
+ }
16
+ }
17
+ function lsSet(key, value) {
18
+ try {
19
+ localStorage.setItem(LS_PREFIX + key, JSON.stringify(value));
20
+ } catch {
21
+ }
22
+ }
23
+ const SIZING_COUNTRIES = [
24
+ { code: "US", label: "United States" },
25
+ { code: "UK", label: "United Kingdom" },
26
+ { code: "EU", label: "Europe (EU)" },
27
+ { code: "FR", label: "France" },
28
+ { code: "IT", label: "Italy" },
29
+ { code: "DE", label: "Germany" },
30
+ { code: "ES", label: "Spain" },
31
+ { code: "JP", label: "Japan" },
32
+ { code: "CN", label: "China" },
33
+ { code: "KR", label: "South Korea" },
34
+ { code: "AU", label: "Australia" },
35
+ { code: "BR", label: "Brazil" }
36
+ ];
37
+ const STEP_LABELS = ["", "Welcome", "Photo", "Size", "Generate", "Results"];
38
+ const TOTAL_STEPS = 5;
39
+ function detectLocale() {
40
+ const l = (navigator.language || "en-US").toLowerCase();
41
+ if (l.includes("en-gb") || l.includes("en-au")) return "UK";
42
+ if (l.includes("en")) return "US";
43
+ if (l.includes("fr")) return "FR";
44
+ if (l.includes("it")) return "IT";
45
+ if (l.includes("de") || l.includes("nl")) return "EU";
46
+ if (l.includes("ja")) return "JP";
47
+ if (l.includes("zh")) return "CN";
48
+ if (l.includes("ko")) return "KR";
49
+ return "US";
50
+ }
51
+ function isImperial(locale) {
52
+ return locale === "US" || locale === "UK";
53
+ }
54
+ function inToCm(inches) {
55
+ return +(inches * 2.54).toFixed(1);
56
+ }
57
+ function lbsToKg(lbs) {
58
+ return +(lbs / 2.205).toFixed(1);
59
+ }
60
+ function ftInToCm(ft, inch) {
61
+ return +(ft * 30.48 + inch * 2.54).toFixed(1);
62
+ }
8
63
  function CameraIcon({ size = 18 }) {
9
- return /* @__PURE__ */ jsxs(
10
- "svg",
11
- {
12
- width: size,
13
- height: size,
14
- viewBox: "0 0 24 24",
15
- fill: "none",
16
- stroke: "currentColor",
17
- strokeWidth: 2,
18
- strokeLinecap: "round",
19
- strokeLinejoin: "round",
20
- children: [
21
- /* @__PURE__ */ jsx("path", { d: "M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z" }),
22
- /* @__PURE__ */ jsx("circle", { cx: "12", cy: "13", r: "4" })
23
- ]
24
- }
25
- );
64
+ return /* @__PURE__ */ jsxs("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round", children: [
65
+ /* @__PURE__ */ jsx("path", { d: "M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z" }),
66
+ /* @__PURE__ */ jsx("circle", { cx: "12", cy: "13", r: "4" })
67
+ ] });
26
68
  }
27
69
  function UploadIcon({ size = 48 }) {
28
- return /* @__PURE__ */ jsxs(
29
- "svg",
30
- {
31
- width: size,
32
- height: size,
33
- viewBox: "0 0 24 24",
34
- fill: "none",
35
- stroke: "currentColor",
36
- strokeWidth: 1.5,
37
- strokeLinecap: "round",
38
- strokeLinejoin: "round",
39
- children: [
40
- /* @__PURE__ */ jsx("path", { d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" }),
41
- /* @__PURE__ */ jsx("polyline", { points: "17 8 12 3 7 8" }),
42
- /* @__PURE__ */ jsx("line", { x1: "12", y1: "3", x2: "12", y2: "15" })
43
- ]
44
- }
45
- );
70
+ return /* @__PURE__ */ jsxs("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 1.5, strokeLinecap: "round", strokeLinejoin: "round", children: [
71
+ /* @__PURE__ */ jsx("path", { d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" }),
72
+ /* @__PURE__ */ jsx("polyline", { points: "17 8 12 3 7 8" }),
73
+ /* @__PURE__ */ jsx("line", { x1: "12", y1: "3", x2: "12", y2: "15" })
74
+ ] });
46
75
  }
47
76
  function XIcon({ size = 20 }) {
48
- return /* @__PURE__ */ jsxs(
49
- "svg",
50
- {
51
- width: size,
52
- height: size,
53
- viewBox: "0 0 24 24",
54
- fill: "none",
55
- stroke: "currentColor",
56
- strokeWidth: 2,
57
- strokeLinecap: "round",
58
- strokeLinejoin: "round",
59
- children: [
60
- /* @__PURE__ */ jsx("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
61
- /* @__PURE__ */ jsx("line", { x1: "6", y1: "6", x2: "18", y2: "18" })
62
- ]
63
- }
64
- );
77
+ return /* @__PURE__ */ jsxs("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round", children: [
78
+ /* @__PURE__ */ jsx("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
79
+ /* @__PURE__ */ jsx("line", { x1: "6", y1: "6", x2: "18", y2: "18" })
80
+ ] });
65
81
  }
66
82
  function AlertIcon({ size = 48 }) {
67
- return /* @__PURE__ */ jsxs(
68
- "svg",
69
- {
70
- width: size,
71
- height: size,
72
- viewBox: "0 0 24 24",
73
- fill: "none",
74
- stroke: "currentColor",
75
- strokeWidth: 1.5,
76
- strokeLinecap: "round",
77
- strokeLinejoin: "round",
78
- children: [
79
- /* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "10" }),
80
- /* @__PURE__ */ jsx("line", { x1: "12", y1: "8", x2: "12", y2: "12" }),
81
- /* @__PURE__ */ jsx("line", { x1: "12", y1: "16", x2: "12.01", y2: "16" })
82
- ]
83
- }
84
- );
83
+ return /* @__PURE__ */ jsxs("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 1.5, strokeLinecap: "round", strokeLinejoin: "round", children: [
84
+ /* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "10" }),
85
+ /* @__PURE__ */ jsx("line", { x1: "12", y1: "8", x2: "12", y2: "12" }),
86
+ /* @__PURE__ */ jsx("line", { x1: "12", y1: "16", x2: "12.01", y2: "16" })
87
+ ] });
88
+ }
89
+ function ArrowLeftIcon() {
90
+ return /* @__PURE__ */ jsx("svg", { width: 18, height: 18, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx("path", { d: "M15 18l-6-6 6-6" }) });
91
+ }
92
+ function ArrowRightIcon() {
93
+ return /* @__PURE__ */ jsx("svg", { width: 16, height: 16, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx("path", { d: "M5 12h14M12 5l7 7-7 7" }) });
94
+ }
95
+ function UserIcon({ size = 16 }) {
96
+ return /* @__PURE__ */ jsxs("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round", children: [
97
+ /* @__PURE__ */ jsx("path", { d: "M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2" }),
98
+ /* @__PURE__ */ jsx("circle", { cx: "12", cy: "7", r: "4" })
99
+ ] });
100
+ }
101
+ function ClockIcon({ size = 16 }) {
102
+ return /* @__PURE__ */ jsxs("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round", children: [
103
+ /* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "10" }),
104
+ /* @__PURE__ */ jsx("polyline", { points: "12 6 12 12 16 14" })
105
+ ] });
106
+ }
107
+ function RulerIcon({ size = 18 }) {
108
+ return /* @__PURE__ */ jsxs("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round", children: [
109
+ /* @__PURE__ */ jsx("path", { d: "M21.3 15.3a2.4 2.4 0 0 1 0 3.4l-2.6 2.6a2.4 2.4 0 0 1-3.4 0L2.7 8.7a2.41 2.41 0 0 1 0-3.4l2.6-2.6a2.41 2.41 0 0 1 3.4 0Z" }),
110
+ /* @__PURE__ */ jsx("path", { d: "m14.5 12.5 2-2" }),
111
+ /* @__PURE__ */ jsx("path", { d: "m11.5 9.5 2-2" }),
112
+ /* @__PURE__ */ jsx("path", { d: "m8.5 6.5 2-2" }),
113
+ /* @__PURE__ */ jsx("path", { d: "m17.5 15.5 2-2" })
114
+ ] });
115
+ }
116
+ function SparkleIcon({ size = 18 }) {
117
+ return /* @__PURE__ */ jsx("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx("path", { d: "m12 3-1.912 5.813a2 2 0 0 1-1.275 1.275L3 12l5.813 1.912a2 2 0 0 1 1.275 1.275L12 21l1.912-5.813a2 2 0 0 1 1.275-1.275L21 12l-5.813-1.912a2 2 0 0 1-1.275-1.275L12 3Z" }) });
118
+ }
119
+ function TrashIcon({ size = 14 }) {
120
+ return /* @__PURE__ */ jsxs("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round", children: [
121
+ /* @__PURE__ */ jsx("polyline", { points: "3 6 5 6 21 6" }),
122
+ /* @__PURE__ */ jsx("path", { d: "M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" })
123
+ ] });
124
+ }
125
+ function ChevronRightIcon() {
126
+ return /* @__PURE__ */ jsx("svg", { width: 16, height: 16, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx("path", { d: "M9 18l6-6-6-6" }) });
127
+ }
128
+ function CheckIcon({ size = 14 }) {
129
+ return /* @__PURE__ */ jsx("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 3, strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx("polyline", { points: "20 6 9 17 4 12" }) });
85
130
  }
86
131
  function getApiKey() {
87
132
  const key = process.env.NEXT_PUBLIC_PRIMESTYLE_API_KEY ?? "";
88
- if (!key) {
89
- throw new PrimeStyleError(
90
- "Missing NEXT_PUBLIC_PRIMESTYLE_API_KEY in environment variables",
91
- "MISSING_API_KEY"
92
- );
93
- }
133
+ if (!key) throw new PrimeStyleError("Missing NEXT_PUBLIC_PRIMESTYLE_API_KEY", "MISSING_API_KEY");
94
134
  return key;
95
135
  }
96
136
  function getApiUrl(override) {
@@ -98,6 +138,7 @@ function getApiUrl(override) {
98
138
  }
99
139
  function PrimeStyleTryon({
100
140
  productImage,
141
+ productTitle = "Product",
101
142
  buttonText = "Virtual Try-On",
102
143
  apiUrl,
103
144
  showPoweredBy = true,
@@ -119,12 +160,29 @@ function PrimeStyleTryon({
119
160
  const [resultImageUrl, setResultImageUrl] = useState(null);
120
161
  const [errorMessage, setErrorMessage] = useState(null);
121
162
  const [dragOver, setDragOver] = useState(false);
122
- const [countdown, setCountdown] = useState(20);
163
+ const [countdown, setCountdown] = useState(25);
164
+ const [sizingMethod, setSizingMethod] = useState(null);
165
+ const [sizingResult, setSizingResult] = useState(null);
166
+ const [sizeGuide, setSizeGuide] = useState(null);
167
+ const [sizingCountry, setSizingCountry] = useState(detectLocale);
168
+ const imperial = isImperial(sizingCountry);
169
+ const [sizingUnit, setSizingUnit] = useState(imperial ? "in" : "cm");
170
+ const [heightUnit, setHeightUnit] = useState(imperial ? "ft" : "cm");
171
+ const [weightUnit, setWeightUnit] = useState(imperial ? "lbs" : "kg");
172
+ const [formData, setFormData] = useState({});
173
+ const [profiles, setProfiles] = useState(() => lsGet("profiles", []));
174
+ const [history, setHistory] = useState(() => lsGet("history", []));
175
+ const [activeProfileId, setActiveProfileId] = useState(null);
176
+ const [profileSaved, setProfileSaved] = useState(false);
177
+ const [drawer, setDrawer] = useState(null);
178
+ const [profileDetail, setProfileDetail] = useState(null);
123
179
  const fileInputRef = useRef(null);
124
180
  const apiRef = useRef(null);
125
181
  const sseRef = useRef(null);
126
182
  const unsubRef = useRef(null);
127
183
  const pollingRef = useRef(null);
184
+ const completedRef = useRef(false);
185
+ const bodyRef = useRef(null);
128
186
  useEffect(() => {
129
187
  try {
130
188
  const key = getApiKey();
@@ -143,12 +201,12 @@ function PrimeStyleTryon({
143
201
  if (view === "processing") {
144
202
  setCountdown(25);
145
203
  const interval = setInterval(() => {
146
- setCountdown((prev) => {
147
- if (prev <= 1) {
204
+ setCountdown((p) => {
205
+ if (p <= 1) {
148
206
  clearInterval(interval);
149
207
  return 0;
150
208
  }
151
- return prev - 1;
209
+ return p - 1;
152
210
  });
153
211
  }, 1e3);
154
212
  return () => clearInterval(interval);
@@ -177,17 +235,47 @@ function PrimeStyleTryon({
177
235
  };
178
236
  }
179
237
  }, [view]);
238
+ useEffect(() => {
239
+ lsSet("profiles", profiles);
240
+ }, [profiles]);
241
+ useEffect(() => {
242
+ lsSet("history", history);
243
+ }, [history]);
244
+ const stepIndex = useMemo(() => {
245
+ switch (view) {
246
+ case "welcome":
247
+ return 1;
248
+ case "upload":
249
+ return 2;
250
+ case "sizing-choice":
251
+ case "sizing-form":
252
+ return 3;
253
+ case "processing":
254
+ return 4;
255
+ case "result":
256
+ return 5;
257
+ default:
258
+ return 1;
259
+ }
260
+ }, [view]);
180
261
  const handleOpen = useCallback(() => {
181
- setView("upload");
262
+ setView("welcome");
182
263
  onOpen?.();
183
264
  }, [onOpen]);
184
265
  const handleClose = useCallback(() => {
185
266
  setView("idle");
186
267
  setSelectedFile(null);
268
+ setDrawer(null);
269
+ setProfileDetail(null);
187
270
  if (previewUrl) URL.revokeObjectURL(previewUrl);
188
271
  setPreviewUrl(null);
189
272
  setResultImageUrl(null);
190
273
  setErrorMessage(null);
274
+ setSizingMethod(null);
275
+ setSizingResult(null);
276
+ setSizeGuide(null);
277
+ setProfileSaved(false);
278
+ setFormData({});
191
279
  unsubRef.current?.();
192
280
  unsubRef.current = null;
193
281
  if (pollingRef.current) {
@@ -196,32 +284,28 @@ function PrimeStyleTryon({
196
284
  }
197
285
  onClose?.();
198
286
  }, [onClose, previewUrl]);
199
- const handleFileSelect = useCallback(
200
- (file) => {
201
- if (!isValidImageFile(file)) {
202
- setErrorMessage("Please upload a JPEG, PNG, or WebP image.");
203
- setView("error");
204
- onError?.({ message: "Invalid file type", code: "INVALID_FILE" });
205
- return;
206
- }
207
- if (file.size > 10 * 1024 * 1024) {
208
- setErrorMessage("Image must be under 10MB.");
209
- setView("error");
210
- onError?.({ message: "File too large", code: "FILE_TOO_LARGE" });
211
- return;
212
- }
213
- setSelectedFile(file);
214
- setPreviewUrl(URL.createObjectURL(file));
215
- onUpload?.(file);
216
- },
217
- [onUpload, onError]
218
- );
287
+ const handleFileSelect = useCallback((file) => {
288
+ if (!isValidImageFile(file)) {
289
+ setErrorMessage("Please upload a JPEG, PNG, or WebP image.");
290
+ setView("error");
291
+ onError?.({ message: "Invalid file type", code: "INVALID_FILE" });
292
+ return;
293
+ }
294
+ if (file.size > 10 * 1024 * 1024) {
295
+ setErrorMessage("Image must be under 10MB.");
296
+ setView("error");
297
+ onError?.({ message: "File too large", code: "FILE_TOO_LARGE" });
298
+ return;
299
+ }
300
+ setSelectedFile(file);
301
+ setPreviewUrl(URL.createObjectURL(file));
302
+ onUpload?.(file);
303
+ }, [onUpload, onError]);
219
304
  const handleRemovePreview = useCallback(() => {
220
305
  setSelectedFile(null);
221
306
  if (previewUrl) URL.revokeObjectURL(previewUrl);
222
307
  setPreviewUrl(null);
223
308
  }, [previewUrl]);
224
- const completedRef = useRef(false);
225
309
  const cleanupJob = useCallback(() => {
226
310
  if (pollingRef.current) {
227
311
  clearInterval(pollingRef.current);
@@ -230,36 +314,76 @@ function PrimeStyleTryon({
230
314
  unsubRef.current?.();
231
315
  unsubRef.current = null;
232
316
  }, []);
233
- const handleVtoUpdate = useCallback(
234
- (update) => {
235
- if (update.status === "completed" && update.imageUrl) {
236
- setResultImageUrl((prev) => {
237
- if (!prev || prev.startsWith("data:")) return update.imageUrl;
238
- if (!update.imageUrl.startsWith("data:")) return update.imageUrl;
239
- return prev;
240
- });
241
- if (!completedRef.current) {
242
- completedRef.current = true;
243
- cleanupJob();
244
- setView("result");
245
- onComplete?.({ jobId: update.galleryId, imageUrl: update.imageUrl });
246
- }
247
- } else if (update.status === "failed") {
248
- if (!completedRef.current) {
249
- completedRef.current = true;
250
- cleanupJob();
251
- const msg = update.error || "Try-on generation failed";
252
- setErrorMessage(msg);
253
- setView("error");
254
- onError?.({ message: msg });
255
- }
317
+ const handleVtoUpdate = useCallback((update) => {
318
+ if (update.status === "completed" && update.imageUrl) {
319
+ setResultImageUrl((prev) => {
320
+ if (!prev || prev.startsWith("data:")) return update.imageUrl;
321
+ if (!update.imageUrl.startsWith("data:")) return update.imageUrl;
322
+ return prev;
323
+ });
324
+ if (!completedRef.current) {
325
+ completedRef.current = true;
326
+ cleanupJob();
327
+ setView("result");
328
+ onComplete?.({ jobId: update.galleryId, imageUrl: update.imageUrl });
256
329
  }
257
- },
258
- [onComplete, onError, cleanupJob]
259
- );
330
+ } else if (update.status === "failed") {
331
+ if (!completedRef.current) {
332
+ completedRef.current = true;
333
+ cleanupJob();
334
+ const msg = update.error || "Try-on generation failed";
335
+ setErrorMessage(msg);
336
+ setView("error");
337
+ onError?.({ message: msg });
338
+ }
339
+ }
340
+ }, [onComplete, onError, cleanupJob]);
341
+ const submitSizing = useCallback(async () => {
342
+ if (!apiRef.current) return;
343
+ const baseUrl = getApiUrl(apiUrl);
344
+ const key = getApiKey();
345
+ const payload = {
346
+ method: sizingMethod,
347
+ locale: sizingCountry,
348
+ product: { title: productTitle, variants: [] }
349
+ };
350
+ if (sizeGuide?.found) payload.sizeGuide = sizeGuide;
351
+ if (sizingMethod === "exact") {
352
+ const m = { gender: formData.gender || "male" };
353
+ if (formData.height) m.heightCm = heightUnit === "ft" ? ftInToCm(parseFloat(formData.heightFeet || "0"), parseFloat(formData.heightInches || "0")) : parseFloat(formData.height);
354
+ if (formData.weight) m.weightKg = weightUnit === "lbs" ? lbsToKg(parseFloat(formData.weight)) : parseFloat(formData.weight);
355
+ const keys = ["chest", "bust", "waist", "hips", "shoulderWidth", "sleeveLength", "inseam", "neckCircumference", "footLengthCm"];
356
+ for (const k of keys) {
357
+ if (formData[k]) m[k] = sizingUnit === "in" ? inToCm(parseFloat(formData[k])) : parseFloat(formData[k]);
358
+ }
359
+ if (formData.shoeEU) m.shoeEU = formData.shoeEU;
360
+ if (formData.shoeUS) m.shoeUS = formData.shoeUS;
361
+ if (formData.shoeUK) m.shoeUK = formData.shoeUK;
362
+ if (formData.fitPreference) m.fitPreference = formData.fitPreference;
363
+ payload.measurements = m;
364
+ } else {
365
+ payload.quickEstimate = {
366
+ heightCm: heightUnit === "ft" ? ftInToCm(parseFloat(formData.heightFeet || "0"), parseFloat(formData.heightInches || "0")) : parseFloat(formData.height || "0"),
367
+ weightKg: weightUnit === "lbs" ? lbsToKg(parseFloat(formData.weight || "0")) : parseFloat(formData.weight || "0"),
368
+ gender: formData.gender || "male"
369
+ };
370
+ }
371
+ try {
372
+ const res = await fetch(`${baseUrl}/api/v1/sizing/recommend`, {
373
+ method: "POST",
374
+ headers: { "Content-Type": "application/json", Authorization: `Bearer ${key}` },
375
+ body: JSON.stringify(payload)
376
+ });
377
+ if (res.ok) {
378
+ const data = await res.json();
379
+ setSizingResult(data);
380
+ }
381
+ } catch {
382
+ }
383
+ }, [apiUrl, sizingMethod, sizingCountry, formData, heightUnit, weightUnit, sizingUnit, sizeGuide, productTitle]);
260
384
  const handleSubmit = useCallback(async () => {
261
385
  if (!selectedFile || !apiRef.current || !sseRef.current) {
262
- const msg = !apiRef.current ? "Missing NEXT_PUBLIC_PRIMESTYLE_API_KEY in environment variables" : "No file selected";
386
+ const msg = !apiRef.current ? "Missing NEXT_PUBLIC_PRIMESTYLE_API_KEY" : "No file selected";
263
387
  setErrorMessage(msg);
264
388
  setView("error");
265
389
  onError?.({ message: msg, code: "SDK_NOT_CONFIGURED" });
@@ -267,17 +391,12 @@ function PrimeStyleTryon({
267
391
  }
268
392
  completedRef.current = false;
269
393
  setView("processing");
394
+ if (sizingMethod) submitSizing();
270
395
  try {
271
396
  const modelImage = await compressImage(selectedFile);
272
- const response = await apiRef.current.submitTryOn(
273
- modelImage,
274
- productImage
275
- );
397
+ const response = await apiRef.current.submitTryOn(modelImage, productImage);
276
398
  onProcessing?.(response.jobId);
277
- unsubRef.current = sseRef.current.onJob(
278
- response.jobId,
279
- (update) => handleVtoUpdate(update)
280
- );
399
+ unsubRef.current = sseRef.current.onJob(response.jobId, handleVtoUpdate);
281
400
  let attempts = 0;
282
401
  pollingRef.current = setInterval(async () => {
283
402
  if (completedRef.current) {
@@ -294,13 +413,7 @@ function PrimeStyleTryon({
294
413
  try {
295
414
  const status = await apiRef.current.getStatus(response.jobId);
296
415
  if (status.status === "completed" || status.status === "failed") {
297
- handleVtoUpdate({
298
- galleryId: response.jobId,
299
- status: status.status,
300
- imageUrl: status.imageUrl,
301
- error: status.status === "failed" ? status.message : null,
302
- timestamp: Date.now()
303
- });
416
+ handleVtoUpdate({ galleryId: response.jobId, status: status.status, imageUrl: status.imageUrl, error: status.status === "failed" ? status.message : null, timestamp: Date.now() });
304
417
  }
305
418
  } catch {
306
419
  }
@@ -312,7 +425,7 @@ function PrimeStyleTryon({
312
425
  setView("error");
313
426
  onError?.({ message, code });
314
427
  }
315
- }, [selectedFile, productImage, onProcessing, onError, handleVtoUpdate]);
428
+ }, [selectedFile, productImage, onProcessing, onError, handleVtoUpdate, sizingMethod, submitSizing]);
316
429
  const handleDownload = useCallback(() => {
317
430
  if (!resultImageUrl) return;
318
431
  if (resultImageUrl.startsWith("data:")) {
@@ -339,8 +452,103 @@ function PrimeStyleTryon({
339
452
  setPreviewUrl(null);
340
453
  setResultImageUrl(null);
341
454
  setErrorMessage(null);
455
+ setSizingMethod(null);
456
+ setSizingResult(null);
457
+ setProfileSaved(false);
342
458
  setView("upload");
343
459
  }, [previewUrl, cleanupJob]);
460
+ const applyProfile = useCallback((id) => {
461
+ const p = profiles.find((pr) => pr.id === id);
462
+ if (!p) return;
463
+ setActiveProfileId(id);
464
+ const fd = { gender: p.gender || "male" };
465
+ if (p.heightCm) fd.height = String(p.heightCm);
466
+ if (p.weightKg) fd.weight = String(p.weightKg);
467
+ if (p.chest) fd.chest = String(p.chest);
468
+ if (p.bust) fd.bust = String(p.bust);
469
+ if (p.waist) fd.waist = String(p.waist);
470
+ if (p.hips) fd.hips = String(p.hips);
471
+ if (p.shoulderWidth) fd.shoulderWidth = String(p.shoulderWidth);
472
+ if (p.sleeveLength) fd.sleeveLength = String(p.sleeveLength);
473
+ if (p.inseam) fd.inseam = String(p.inseam);
474
+ if (p.neckCircumference) fd.neckCircumference = String(p.neckCircumference);
475
+ if (p.footLengthCm) fd.footLengthCm = String(p.footLengthCm);
476
+ if (p.shoeEU) fd.shoeEU = p.shoeEU;
477
+ if (p.shoeUS) fd.shoeUS = p.shoeUS;
478
+ if (p.shoeUK) fd.shoeUK = p.shoeUK;
479
+ if (p.fitPreference) fd.fitPreference = p.fitPreference;
480
+ setFormData(fd);
481
+ }, [profiles]);
482
+ const saveProfile = useCallback((name) => {
483
+ const id = activeProfileId || `p_${Date.now()}`;
484
+ const p = {
485
+ id,
486
+ name,
487
+ gender: formData.gender || "male",
488
+ heightCm: formData.height ? parseFloat(formData.height) : void 0,
489
+ weightKg: formData.weight ? parseFloat(formData.weight) : void 0,
490
+ chest: formData.chest ? parseFloat(formData.chest) : void 0,
491
+ bust: formData.bust ? parseFloat(formData.bust) : void 0,
492
+ waist: formData.waist ? parseFloat(formData.waist) : void 0,
493
+ hips: formData.hips ? parseFloat(formData.hips) : void 0,
494
+ shoulderWidth: formData.shoulderWidth ? parseFloat(formData.shoulderWidth) : void 0,
495
+ sleeveLength: formData.sleeveLength ? parseFloat(formData.sleeveLength) : void 0,
496
+ inseam: formData.inseam ? parseFloat(formData.inseam) : void 0,
497
+ neckCircumference: formData.neckCircumference ? parseFloat(formData.neckCircumference) : void 0,
498
+ footLengthCm: formData.footLengthCm ? parseFloat(formData.footLengthCm) : void 0,
499
+ shoeEU: formData.shoeEU,
500
+ shoeUS: formData.shoeUS,
501
+ shoeUK: formData.shoeUK,
502
+ fitPreference: formData.fitPreference,
503
+ createdAt: Date.now()
504
+ };
505
+ setProfiles((prev) => {
506
+ const idx = prev.findIndex((x) => x.id === id);
507
+ if (idx >= 0) {
508
+ const next = [...prev];
509
+ next[idx] = p;
510
+ return next;
511
+ }
512
+ return [...prev, p].slice(-50);
513
+ });
514
+ setActiveProfileId(id);
515
+ setProfileSaved(true);
516
+ }, [activeProfileId, formData]);
517
+ const saveHistoryEntry = useCallback(() => {
518
+ const entry = {
519
+ id: `h_${Date.now()}`,
520
+ productTitle,
521
+ productImage,
522
+ resultImageUrl: resultImageUrl || void 0,
523
+ recommendedSize: sizingResult?.recommendedSize,
524
+ confidence: sizingResult?.confidence,
525
+ reasoning: sizingResult?.reasoning,
526
+ internationalSizes: sizingResult?.internationalSizes,
527
+ matchDetails: sizingResult?.matchDetails,
528
+ date: Date.now()
529
+ };
530
+ if (activeProfileId) {
531
+ const p = profiles.find((x) => x.id === activeProfileId);
532
+ if (p) entry.profileName = p.name;
533
+ }
534
+ setHistory((prev) => [entry, ...prev].slice(0, 50));
535
+ }, [productTitle, productImage, resultImageUrl, sizingResult, activeProfileId, profiles]);
536
+ useEffect(() => {
537
+ if (view === "result" && (resultImageUrl || sizingResult)) {
538
+ saveHistoryEntry();
539
+ }
540
+ }, [view]);
541
+ const updateField = useCallback((key, val) => {
542
+ setFormData((prev) => ({ ...prev, [key]: val }));
543
+ }, []);
544
+ const shoeField = useMemo(() => {
545
+ const map = {
546
+ US: { key: "shoeUS", label: "Shoe size (US)", ph: "e.g. 10" },
547
+ UK: { key: "shoeUK", label: "Shoe size (UK)", ph: "e.g. 9" },
548
+ AU: { key: "shoeUK", label: "Shoe size (UK)", ph: "e.g. 9" }
549
+ };
550
+ return map[sizingCountry] || { key: "shoeEU", label: "Shoe size (EU)", ph: "e.g. 43" };
551
+ }, [sizingCountry]);
344
552
  const rootVars = {
345
553
  "--ps-btn-bg": btnS.backgroundColor,
346
554
  "--ps-btn-color": btnS.textColor,
@@ -377,520 +585,847 @@ function PrimeStyleTryon({
377
585
  "--ps-loader": mdlS.loaderColor,
378
586
  "--ps-result-radius": mdlS.resultBorderRadius
379
587
  };
380
- const cssVars = Object.fromEntries(
381
- Object.entries(rootVars).filter(([, v]) => v !== void 0)
382
- );
383
- return /* @__PURE__ */ jsxs(
384
- "div",
385
- {
386
- className: cx("ps-tryon-root", cn.root || className),
387
- style: { ...cssVars, ...style },
388
- "data-ps-tryon": true,
389
- children: [
390
- /* @__PURE__ */ jsxs("button", { onClick: handleOpen, className: cx("ps-tryon-btn", cn.button), children: [
391
- /* @__PURE__ */ jsx(CameraIcon, { size: parseInt(btnS.iconSize || "18") }),
392
- /* @__PURE__ */ jsx("span", { children: buttonText })
588
+ const cssVars = Object.fromEntries(Object.entries(rootVars).filter(([, v]) => v !== void 0));
589
+ function Stepper() {
590
+ if (view === "error" || view === "idle") return null;
591
+ return /* @__PURE__ */ jsx("div", { className: "ps-tryon-stepper", children: /* @__PURE__ */ jsx("div", { className: "ps-tryon-stepper-track", children: Array.from({ length: TOTAL_STEPS }, (_, i) => i + 1).map((i) => /* @__PURE__ */ jsxs("div", { className: "ps-tryon-stepper-fragment", children: [
592
+ i > 1 && /* @__PURE__ */ jsx("div", { className: `ps-tryon-stepper-line${i <= stepIndex ? " ps-done" : ""}` }),
593
+ /* @__PURE__ */ jsxs("div", { className: `ps-tryon-stepper-step${i < stepIndex ? " ps-done" : i === stepIndex ? " ps-active" : ""}`, children: [
594
+ /* @__PURE__ */ jsx("div", { className: "ps-tryon-stepper-circle", children: i < stepIndex ? /* @__PURE__ */ jsx(CheckIcon, {}) : i }),
595
+ /* @__PURE__ */ jsx("div", { className: "ps-tryon-stepper-label", children: STEP_LABELS[i] })
596
+ ] })
597
+ ] }, i)) }) });
598
+ }
599
+ function InputRow({ label, fieldKey, placeholder, type = "text", unit }) {
600
+ return /* @__PURE__ */ jsxs("div", { className: "ps-tryon-input-row", children: [
601
+ /* @__PURE__ */ jsx("label", { children: label }),
602
+ /* @__PURE__ */ jsx(
603
+ "input",
604
+ {
605
+ type,
606
+ placeholder,
607
+ value: formData[fieldKey] || "",
608
+ onChange: (e) => updateField(fieldKey, e.target.value)
609
+ }
610
+ ),
611
+ unit && /* @__PURE__ */ jsx("span", { className: "ps-tryon-input-unit", children: unit })
612
+ ] });
613
+ }
614
+ function UnitToggle({ options, value, onChange }) {
615
+ return /* @__PURE__ */ jsx("div", { className: "ps-tryon-unit-toggle", children: options.map((o) => /* @__PURE__ */ jsx("button", { className: `ps-tryon-unit-btn${value === o.value ? " ps-active" : ""}`, onClick: () => onChange(o.value), children: o.label }, o.value)) });
616
+ }
617
+ function WelcomeView() {
618
+ return /* @__PURE__ */ jsxs("div", { className: "ps-tryon-welcome", children: [
619
+ /* @__PURE__ */ jsxs("div", { className: "ps-tryon-welcome-hero", children: [
620
+ productImage && /* @__PURE__ */ jsxs("div", { className: "ps-tryon-welcome-img-wrap", children: [
621
+ /* @__PURE__ */ jsx("img", { src: productImage, alt: "Product", className: "ps-tryon-welcome-product" }),
622
+ /* @__PURE__ */ jsx("div", { className: "ps-tryon-welcome-sparkle", children: /* @__PURE__ */ jsx(SparkleIcon, { size: 20 }) })
393
623
  ] }),
394
- view !== "idle" && /* @__PURE__ */ jsx(
395
- "div",
396
- {
397
- className: cx("ps-tryon-overlay", cn.overlay),
398
- onClick: (e) => {
399
- if (e.target === e.currentTarget) handleClose();
400
- },
401
- children: /* @__PURE__ */ jsxs(
402
- "div",
624
+ /* @__PURE__ */ jsx("h2", { className: "ps-tryon-welcome-title", children: "See How It Looks On You" }),
625
+ /* @__PURE__ */ jsx("p", { className: "ps-tryon-welcome-sub", children: "Virtual try-on + AI size recommendation" })
626
+ ] }),
627
+ /* @__PURE__ */ jsx("div", { className: "ps-tryon-features", children: [
628
+ { icon: /* @__PURE__ */ jsx(CameraIcon, {}), title: "Upload Photo", desc: "Share a photo of yourself" },
629
+ { icon: /* @__PURE__ */ jsx(RulerIcon, {}), title: "Find Your Size", desc: "AI-powered fit analysis" },
630
+ { icon: /* @__PURE__ */ jsx(SparkleIcon, {}), title: "See Results", desc: "Try-on in seconds" }
631
+ ].map((f, i) => /* @__PURE__ */ jsxs("div", { className: "ps-tryon-feature", children: [
632
+ /* @__PURE__ */ jsx("div", { className: "ps-tryon-feature-icon", children: f.icon }),
633
+ /* @__PURE__ */ jsx("div", { className: "ps-tryon-feature-title", children: f.title }),
634
+ /* @__PURE__ */ jsx("div", { className: "ps-tryon-feature-desc", children: f.desc })
635
+ ] }, i)) }),
636
+ /* @__PURE__ */ jsxs("button", { className: "ps-tryon-cta", onClick: () => setView("upload"), children: [
637
+ "Let’s Get Started ",
638
+ /* @__PURE__ */ jsx(ArrowRightIcon, {})
639
+ ] }),
640
+ /* @__PURE__ */ jsx("div", { className: "ps-tryon-welcome-note", children: "Takes less than a minute" })
641
+ ] });
642
+ }
643
+ function UploadView() {
644
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
645
+ profiles.length > 0 && /* @__PURE__ */ jsx("div", { className: "ps-tryon-profile-bar", children: /* @__PURE__ */ jsxs("select", { className: "ps-tryon-profile-select", value: activeProfileId || "", onChange: (e) => {
646
+ if (e.target.value) applyProfile(e.target.value);
647
+ }, children: [
648
+ /* @__PURE__ */ jsx("option", { value: "", children: "Auto-fill from saved profile..." }),
649
+ profiles.map((p) => /* @__PURE__ */ jsxs("option", { value: p.id, children: [
650
+ p.name,
651
+ " (",
652
+ p.gender === "female" ? "Women's" : "Men's",
653
+ ")"
654
+ ] }, p.id))
655
+ ] }) }),
656
+ selectedFile && previewUrl ? /* @__PURE__ */ jsxs(Fragment, { children: [
657
+ /* @__PURE__ */ jsxs("div", { className: cx("ps-tryon-preview", cn.preview), children: [
658
+ /* @__PURE__ */ jsx("img", { src: previewUrl, alt: "Your photo", className: cn.previewImage }),
659
+ /* @__PURE__ */ jsx("button", { onClick: handleRemovePreview, className: cx("ps-tryon-preview-remove", cn.removeButton), children: "×" })
660
+ ] }),
661
+ /* @__PURE__ */ jsxs("button", { onClick: () => setView("sizing-choice"), className: cx("ps-tryon-submit", cn.submitButton), children: [
662
+ "Continue to Sizing ",
663
+ /* @__PURE__ */ jsx(ArrowRightIcon, {})
664
+ ] })
665
+ ] }) : /* @__PURE__ */ jsxs(
666
+ "div",
667
+ {
668
+ className: cx(`ps-tryon-upload${dragOver ? " ps-tryon-drag-over" : ""}`, cn.uploadZone),
669
+ onClick: () => fileInputRef.current?.click(),
670
+ onDragOver: (e) => {
671
+ e.preventDefault();
672
+ setDragOver(true);
673
+ },
674
+ onDragLeave: () => setDragOver(false),
675
+ onDrop: (e) => {
676
+ e.preventDefault();
677
+ setDragOver(false);
678
+ const f = e.dataTransfer?.files?.[0];
679
+ if (f) handleFileSelect(f);
680
+ },
681
+ children: [
682
+ /* @__PURE__ */ jsx(
683
+ "input",
403
684
  {
404
- className: cx("ps-tryon-modal", cn.modal),
405
- onClick: (e) => e.stopPropagation(),
406
- children: [
407
- /* @__PURE__ */ jsxs("div", { className: cx("ps-tryon-header", cn.header), children: [
408
- /* @__PURE__ */ jsx("span", { className: cx("ps-tryon-title", cn.title), children: "Virtual Try-On" }),
409
- /* @__PURE__ */ jsx(
410
- "button",
411
- {
412
- onClick: handleClose,
413
- className: cx("ps-tryon-close", cn.closeButton),
414
- children: /* @__PURE__ */ jsx(XIcon, {})
415
- }
416
- )
417
- ] }),
418
- /* @__PURE__ */ jsxs("div", { className: cx("ps-tryon-body", cn.body), children: [
419
- view === "upload" && /* @__PURE__ */ jsx(Fragment, { children: selectedFile && previewUrl ? /* @__PURE__ */ jsxs(Fragment, { children: [
420
- /* @__PURE__ */ jsxs("div", { className: cx("ps-tryon-preview", cn.preview), children: [
421
- /* @__PURE__ */ jsx(
422
- "img",
423
- {
424
- src: previewUrl,
425
- alt: "Your photo",
426
- className: cn.previewImage
427
- }
428
- ),
429
- /* @__PURE__ */ jsx(
430
- "button",
431
- {
432
- onClick: handleRemovePreview,
433
- className: cx(
434
- "ps-tryon-preview-remove",
435
- cn.removeButton
436
- ),
437
- children: "×"
438
- }
439
- )
440
- ] }),
441
- /* @__PURE__ */ jsx(
442
- "button",
443
- {
444
- onClick: handleSubmit,
445
- className: cx("ps-tryon-submit", cn.submitButton),
446
- children: "Try It On"
447
- }
448
- )
449
- ] }) : /* @__PURE__ */ jsxs(
450
- "div",
451
- {
452
- className: cx(
453
- `ps-tryon-upload${dragOver ? " ps-tryon-drag-over" : ""}`,
454
- cn.uploadZone
455
- ),
456
- onClick: () => fileInputRef.current?.click(),
457
- onDragOver: (e) => {
458
- e.preventDefault();
459
- setDragOver(true);
460
- },
461
- onDragLeave: () => setDragOver(false),
462
- onDrop: (e) => {
463
- e.preventDefault();
464
- setDragOver(false);
465
- const file = e.dataTransfer?.files?.[0];
466
- if (file) handleFileSelect(file);
467
- },
468
- children: [
469
- /* @__PURE__ */ jsx(
470
- "input",
471
- {
472
- ref: fileInputRef,
473
- type: "file",
474
- accept: "image/jpeg,image/png,image/webp",
475
- style: { display: "none" },
476
- onChange: (e) => {
477
- const file = e.target.files?.[0];
478
- if (file) handleFileSelect(file);
479
- }
480
- }
481
- ),
482
- /* @__PURE__ */ jsx(UploadIcon, {}),
483
- /* @__PURE__ */ jsx("p", { className: cx("ps-tryon-upload-text", cn.uploadText), children: "Drop or upload your full body photo!" }),
484
- /* @__PURE__ */ jsx("p", { className: cx("ps-tryon-upload-hint", cn.uploadHint), children: "JPEG, PNG or WebP (max 10MB)" })
485
- ]
486
- }
487
- ) }),
488
- view === "processing" && /* @__PURE__ */ jsxs("div", { className: "ps-tryon-processing", children: [
489
- countdown > 0 ? /* @__PURE__ */ jsxs("div", { className: "ps-tryon-countdown-ring", children: [
490
- /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 120 120", children: [
491
- /* @__PURE__ */ jsx(
492
- "circle",
493
- {
494
- className: "ps-tryon-countdown-track",
495
- cx: "60",
496
- cy: "60",
497
- r: "52"
498
- }
499
- ),
500
- /* @__PURE__ */ jsx(
501
- "circle",
502
- {
503
- className: "ps-tryon-countdown-progress",
504
- cx: "60",
505
- cy: "60",
506
- r: "52",
507
- style: {
508
- strokeDashoffset: `${countdown / 25 * 326.73}`
509
- }
510
- }
511
- )
512
- ] }),
513
- /* @__PURE__ */ jsx("span", { className: "ps-tryon-countdown-number", children: countdown })
514
- ] }) : /* @__PURE__ */ jsx("div", { className: "ps-tryon-done-pulse", children: /* @__PURE__ */ jsx("div", { className: cx("ps-tryon-spinner", cn.spinner) }) }),
515
- /* @__PURE__ */ jsx(
516
- "p",
517
- {
518
- className: cx(
519
- "ps-tryon-processing-text",
520
- cn.processingText
521
- ),
522
- children: countdown > 0 ? "Generating your try-on..." : "Almost there..."
523
- }
524
- ),
525
- /* @__PURE__ */ jsx(
526
- "p",
527
- {
528
- className: cx(
529
- "ps-tryon-processing-sub",
530
- cn.processingSubText
531
- ),
532
- children: countdown > 0 ? "This usually takes 15-25 seconds" : "Finishing up your look"
533
- }
534
- )
535
- ] }),
536
- view === "result" && resultImageUrl && /* @__PURE__ */ jsxs("div", { className: cx("ps-tryon-result", cn.result), children: [
537
- /* @__PURE__ */ jsx(
538
- "img",
539
- {
540
- src: resultImageUrl,
541
- alt: "Try-on result",
542
- className: cn.resultImage
543
- }
544
- ),
545
- /* @__PURE__ */ jsxs(
546
- "div",
547
- {
548
- className: cx(
549
- "ps-tryon-result-actions",
550
- cn.resultActions
551
- ),
552
- children: [
553
- /* @__PURE__ */ jsx(
554
- "button",
555
- {
556
- onClick: handleDownload,
557
- className: cx("ps-tryon-btn-download", cn.downloadButton),
558
- children: "Download"
559
- }
560
- ),
561
- /* @__PURE__ */ jsx(
562
- "button",
563
- {
564
- onClick: handleRetry,
565
- className: cx("ps-tryon-btn-retry", cn.retryButton),
566
- children: "Try Another"
567
- }
568
- )
569
- ]
570
- }
571
- )
572
- ] }),
573
- view === "error" && /* @__PURE__ */ jsxs("div", { className: cx("ps-tryon-error", cn.error), children: [
574
- /* @__PURE__ */ jsx(AlertIcon, {}),
575
- /* @__PURE__ */ jsx("p", { className: cx("ps-tryon-error-text", cn.errorText), children: errorMessage || "Something went wrong" }),
576
- /* @__PURE__ */ jsx(
577
- "button",
578
- {
579
- onClick: handleRetry,
580
- className: cx("ps-tryon-submit", cn.submitButton),
581
- children: "Try Again"
582
- }
583
- )
584
- ] })
585
- ] }),
586
- showPoweredBy && /* @__PURE__ */ jsxs("div", { className: cx("ps-tryon-powered", cn.poweredBy), children: [
587
- "Powered by",
588
- " ",
589
- /* @__PURE__ */ jsx(
590
- "a",
591
- {
592
- href: "https://myaifitting.com",
593
- target: "_blank",
594
- rel: "noopener noreferrer",
595
- children: "PrimeStyle AI"
596
- }
597
- )
598
- ] })
599
- ]
685
+ ref: fileInputRef,
686
+ type: "file",
687
+ accept: "image/jpeg,image/png,image/webp",
688
+ style: { display: "none" },
689
+ onChange: (e) => {
690
+ const f = e.target.files?.[0];
691
+ if (f) handleFileSelect(f);
692
+ }
600
693
  }
601
- )
602
- }
603
- ),
604
- /* @__PURE__ */ jsx("style", { children: `
605
- .ps-tryon-root {
606
- display: inline-block;
694
+ ),
695
+ /* @__PURE__ */ jsx(UploadIcon, {}),
696
+ /* @__PURE__ */ jsx("p", { className: cx("ps-tryon-upload-text", cn.uploadText), children: "Drop or upload your full body photo!" }),
697
+ /* @__PURE__ */ jsx("p", { className: cx("ps-tryon-upload-hint", cn.uploadHint), children: "JPEG, PNG or WebP (max 10MB)" })
698
+ ]
607
699
  }
700
+ )
701
+ ] });
702
+ }
703
+ function SizingChoiceView() {
704
+ return /* @__PURE__ */ jsxs("div", { className: "ps-tryon-sizing-choice", children: [
705
+ /* @__PURE__ */ jsx("h3", { className: "ps-tryon-section-title", children: "How would you like to find your size?" }),
706
+ /* @__PURE__ */ jsxs("div", { className: "ps-tryon-choice-cards", children: [
707
+ /* @__PURE__ */ jsxs("button", { className: "ps-tryon-choice-card", onClick: () => {
708
+ setSizingMethod("exact");
709
+ setView("sizing-form");
710
+ }, children: [
711
+ /* @__PURE__ */ jsx("div", { className: "ps-tryon-choice-icon", children: /* @__PURE__ */ jsx(RulerIcon, { size: 24 }) }),
712
+ /* @__PURE__ */ jsx("div", { className: "ps-tryon-choice-title", children: "Exact Measurements" }),
713
+ /* @__PURE__ */ jsx("div", { className: "ps-tryon-choice-desc", children: "Enter your body measurements for the most accurate fit" }),
714
+ /* @__PURE__ */ jsx("span", { className: "ps-tryon-choice-badge", children: "Most Accurate" })
715
+ ] }),
716
+ /* @__PURE__ */ jsxs("button", { className: "ps-tryon-choice-card", onClick: () => {
717
+ setSizingMethod("quick");
718
+ setView("sizing-form");
719
+ }, children: [
720
+ /* @__PURE__ */ jsx("div", { className: "ps-tryon-choice-icon", children: /* @__PURE__ */ jsx(SparkleIcon, { size: 24 }) }),
721
+ /* @__PURE__ */ jsx("div", { className: "ps-tryon-choice-title", children: "Quick Estimate" }),
722
+ /* @__PURE__ */ jsx("div", { className: "ps-tryon-choice-desc", children: "Just height, weight & gender — fast but less precise" })
723
+ ] })
724
+ ] }),
725
+ /* @__PURE__ */ jsx("button", { className: "ps-tryon-skip-btn", onClick: () => {
726
+ setSizingMethod(null);
727
+ handleSubmit();
728
+ }, children: "Skip sizing — just try it on" })
729
+ ] });
730
+ }
731
+ function SizingFormView() {
732
+ const isFemale = formData.gender === "female";
733
+ return /* @__PURE__ */ jsxs("div", { className: "ps-tryon-sizing-form", children: [
734
+ /* @__PURE__ */ jsxs("div", { className: "ps-tryon-input-row", children: [
735
+ /* @__PURE__ */ jsx("label", { children: "Sizing region" }),
736
+ /* @__PURE__ */ jsx("select", { className: "ps-tryon-country-select", value: sizingCountry, onChange: (e) => setSizingCountry(e.target.value), children: SIZING_COUNTRIES.map((c) => /* @__PURE__ */ jsx("option", { value: c.code, children: c.label }, c.code)) })
737
+ ] }),
738
+ /* @__PURE__ */ jsxs("div", { className: "ps-tryon-input-row", children: [
739
+ /* @__PURE__ */ jsx("label", { children: "Gender" }),
740
+ /* @__PURE__ */ jsxs("div", { className: "ps-tryon-unit-toggle", children: [
741
+ /* @__PURE__ */ jsx("button", { className: `ps-tryon-unit-btn${formData.gender !== "female" ? " ps-active" : ""}`, onClick: () => updateField("gender", "male"), children: "Men's" }),
742
+ /* @__PURE__ */ jsx("button", { className: `ps-tryon-unit-btn${formData.gender === "female" ? " ps-active" : ""}`, onClick: () => updateField("gender", "female"), children: "Women's" })
743
+ ] })
744
+ ] }),
745
+ /* @__PURE__ */ jsxs("div", { className: "ps-tryon-input-row", children: [
746
+ /* @__PURE__ */ jsx("label", { children: "Height" }),
747
+ /* @__PURE__ */ jsx(UnitToggle, { options: [{ label: "cm", value: "cm" }, { label: "ft/in", value: "ft" }], value: heightUnit, onChange: setHeightUnit })
748
+ ] }),
749
+ heightUnit === "ft" ? /* @__PURE__ */ jsxs("div", { className: "ps-tryon-input-row ps-tryon-dual-input", children: [
750
+ /* @__PURE__ */ jsx("input", { type: "number", placeholder: "ft", value: formData.heightFeet || "", onChange: (e) => updateField("heightFeet", e.target.value) }),
751
+ /* @__PURE__ */ jsx("input", { type: "number", placeholder: "in", value: formData.heightInches || "", onChange: (e) => updateField("heightInches", e.target.value) })
752
+ ] }) : /* @__PURE__ */ jsx(InputRow, { label: "", fieldKey: "height", placeholder: "e.g. 175", type: "number", unit: "cm" }),
753
+ /* @__PURE__ */ jsxs("div", { className: "ps-tryon-input-row", children: [
754
+ /* @__PURE__ */ jsx("label", { children: "Weight" }),
755
+ /* @__PURE__ */ jsx(UnitToggle, { options: [{ label: "kg", value: "kg" }, { label: "lbs", value: "lbs" }], value: weightUnit, onChange: setWeightUnit })
756
+ ] }),
757
+ /* @__PURE__ */ jsx(InputRow, { label: "", fieldKey: "weight", placeholder: weightUnit === "lbs" ? "e.g. 170" : "e.g. 75", type: "number", unit: weightUnit }),
758
+ sizingMethod === "exact" && /* @__PURE__ */ jsxs(Fragment, { children: [
759
+ /* @__PURE__ */ jsxs("div", { className: "ps-tryon-input-row", children: [
760
+ /* @__PURE__ */ jsx("label", { children: "Measurements" }),
761
+ /* @__PURE__ */ jsx(UnitToggle, { options: [{ label: "cm", value: "cm" }, { label: "in", value: "in" }], value: sizingUnit, onChange: setSizingUnit })
762
+ ] }),
763
+ isFemale ? /* @__PURE__ */ jsxs(Fragment, { children: [
764
+ /* @__PURE__ */ jsx(InputRow, { label: "Bust", fieldKey: "bust", placeholder: "e.g. 90", type: "number", unit: sizingUnit }),
765
+ /* @__PURE__ */ jsx(InputRow, { label: "Waist", fieldKey: "waist", placeholder: "e.g. 70", type: "number", unit: sizingUnit }),
766
+ /* @__PURE__ */ jsx(InputRow, { label: "Hips", fieldKey: "hips", placeholder: "e.g. 95", type: "number", unit: sizingUnit })
767
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
768
+ /* @__PURE__ */ jsx(InputRow, { label: "Chest", fieldKey: "chest", placeholder: "e.g. 100", type: "number", unit: sizingUnit }),
769
+ /* @__PURE__ */ jsx(InputRow, { label: "Waist", fieldKey: "waist", placeholder: "e.g. 82", type: "number", unit: sizingUnit }),
770
+ /* @__PURE__ */ jsx(InputRow, { label: "Hips", fieldKey: "hips", placeholder: "e.g. 98", type: "number", unit: sizingUnit })
771
+ ] }),
772
+ /* @__PURE__ */ jsx(InputRow, { label: "Shoulder Width", fieldKey: "shoulderWidth", placeholder: "e.g. 45", type: "number", unit: sizingUnit }),
773
+ /* @__PURE__ */ jsx(InputRow, { label: "Sleeve Length", fieldKey: "sleeveLength", placeholder: "e.g. 65", type: "number", unit: sizingUnit }),
774
+ /* @__PURE__ */ jsx(InputRow, { label: "Inseam", fieldKey: "inseam", placeholder: "e.g. 80", type: "number", unit: sizingUnit }),
775
+ isFemale && /* @__PURE__ */ jsxs("div", { className: "ps-tryon-input-row", children: [
776
+ /* @__PURE__ */ jsx("label", { children: "Fit preference" }),
777
+ /* @__PURE__ */ jsx("div", { className: "ps-tryon-unit-toggle", children: ["petite", "standard", "tall", "plus"].map((fp) => /* @__PURE__ */ jsx(
778
+ "button",
779
+ {
780
+ className: `ps-tryon-unit-btn${(formData.fitPreference || "standard") === fp ? " ps-active" : ""}`,
781
+ onClick: () => updateField("fitPreference", fp),
782
+ children: fp.charAt(0).toUpperCase() + fp.slice(1)
783
+ },
784
+ fp
785
+ )) })
786
+ ] }),
787
+ /* @__PURE__ */ jsxs("div", { className: "ps-tryon-shoe-section", children: [
788
+ /* @__PURE__ */ jsx("div", { className: "ps-tryon-shoe-title", children: "Shoe sizing (optional)" }),
789
+ /* @__PURE__ */ jsx(InputRow, { label: "Foot length", fieldKey: "footLengthCm", placeholder: sizingUnit === "in" ? "e.g. 10.5" : "e.g. 27", type: "number", unit: sizingUnit }),
790
+ /* @__PURE__ */ jsx(InputRow, { label: shoeField.label, fieldKey: shoeField.key, placeholder: shoeField.ph })
791
+ ] })
792
+ ] }),
793
+ /* @__PURE__ */ jsx("p", { className: "ps-tryon-disclaimer", children: "Fill in what you know — more measurements = better accuracy." }),
794
+ /* @__PURE__ */ jsxs("button", { className: "ps-tryon-submit", onClick: handleSubmit, children: [
795
+ "Get My Size & Try On ",
796
+ /* @__PURE__ */ jsx(ArrowRightIcon, {})
797
+ ] })
798
+ ] });
799
+ }
800
+ function ProcessingView() {
801
+ return /* @__PURE__ */ jsxs("div", { className: "ps-tryon-processing", children: [
802
+ countdown > 0 ? /* @__PURE__ */ jsxs("div", { className: "ps-tryon-countdown-ring", children: [
803
+ /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 120 120", children: [
804
+ /* @__PURE__ */ jsx("circle", { className: "ps-tryon-countdown-track", cx: "60", cy: "60", r: "52" }),
805
+ /* @__PURE__ */ jsx("circle", { className: "ps-tryon-countdown-progress", cx: "60", cy: "60", r: "52", style: { strokeDashoffset: `${countdown / 25 * 326.73}` } })
806
+ ] }),
807
+ /* @__PURE__ */ jsx("span", { className: "ps-tryon-countdown-number", children: countdown })
808
+ ] }) : /* @__PURE__ */ jsx("div", { className: "ps-tryon-done-pulse", children: /* @__PURE__ */ jsx("div", { className: cx("ps-tryon-spinner", cn.spinner) }) }),
809
+ /* @__PURE__ */ jsx("p", { className: cx("ps-tryon-processing-text", cn.processingText), children: countdown > 0 ? "Generating your try-on..." : "Almost there..." }),
810
+ /* @__PURE__ */ jsx("p", { className: cx("ps-tryon-processing-sub", cn.processingSubText), children: countdown > 0 ? "This usually takes 15-25 seconds" : "Finishing up your look" })
811
+ ] });
812
+ }
813
+ function ResultView() {
814
+ const hasBoth = !!resultImageUrl && !!sizingResult;
815
+ const [profileName, setProfileName] = useState("");
816
+ return /* @__PURE__ */ jsxs("div", { className: `ps-tryon-result-layout${hasBoth ? " ps-tryon-result-split" : ""}`, children: [
817
+ resultImageUrl && /* @__PURE__ */ jsx("div", { className: "ps-tryon-result-image-col", children: /* @__PURE__ */ jsx("img", { src: resultImageUrl, alt: "Try-on result", className: cn.resultImage }) }),
818
+ /* @__PURE__ */ jsxs("div", { className: "ps-tryon-result-info-col", children: [
819
+ sizingResult && /* @__PURE__ */ jsxs("div", { className: "ps-tryon-size-recommend", children: [
820
+ /* @__PURE__ */ jsx("div", { className: "ps-tryon-size-badge", children: sizingResult.recommendedSize }),
821
+ /* @__PURE__ */ jsxs("div", { className: "ps-tryon-size-confidence", children: [
822
+ "Confidence: ",
823
+ /* @__PURE__ */ jsx("span", { className: `ps-conf-${sizingResult.confidence}`, children: sizingResult.confidence })
824
+ ] }),
825
+ /* @__PURE__ */ jsx("p", { className: "ps-tryon-size-reasoning", children: sizingResult.reasoning }),
826
+ sizingResult.internationalSizes && /* @__PURE__ */ jsx("div", { className: "ps-tryon-intl-sizes", children: Object.entries(sizingResult.internationalSizes).map(([k, v]) => /* @__PURE__ */ jsxs("span", { className: "ps-tryon-intl-chip", children: [
827
+ k,
828
+ ": ",
829
+ v
830
+ ] }, k)) }),
831
+ sizingResult.matchDetails && sizingResult.matchDetails.length > 0 && /* @__PURE__ */ jsxs("details", { className: "ps-tryon-match-details", children: [
832
+ /* @__PURE__ */ jsx("summary", { children: "Measurement breakdown" }),
833
+ /* @__PURE__ */ jsxs("table", { children: [
834
+ /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { children: [
835
+ /* @__PURE__ */ jsx("th", { children: "Area" }),
836
+ /* @__PURE__ */ jsx("th", { children: "You" }),
837
+ /* @__PURE__ */ jsx("th", { children: "Chart" }),
838
+ /* @__PURE__ */ jsx("th", { children: "Fit" })
839
+ ] }) }),
840
+ /* @__PURE__ */ jsx("tbody", { children: sizingResult.matchDetails.map((m, i) => /* @__PURE__ */ jsxs("tr", { children: [
841
+ /* @__PURE__ */ jsx("td", { children: m.measurement }),
842
+ /* @__PURE__ */ jsx("td", { children: m.userValue }),
843
+ /* @__PURE__ */ jsx("td", { children: m.chartRange }),
844
+ /* @__PURE__ */ jsx("td", { className: `ps-fit-${m.fit}`, children: m.fit })
845
+ ] }, i)) })
846
+ ] })
847
+ ] })
848
+ ] }),
849
+ /* @__PURE__ */ jsxs("div", { className: cx("ps-tryon-result-actions", cn.resultActions), children: [
850
+ /* @__PURE__ */ jsx("button", { onClick: handleDownload, className: cx("ps-tryon-btn-download", cn.downloadButton), children: "Download" }),
851
+ /* @__PURE__ */ jsx("button", { onClick: handleRetry, className: cx("ps-tryon-btn-retry", cn.retryButton), children: "Try Another" })
852
+ ] }),
853
+ sizingMethod && !profileSaved && /* @__PURE__ */ jsxs("div", { className: "ps-tryon-save-prompt", children: [
854
+ /* @__PURE__ */ jsx("input", { type: "text", placeholder: "Profile name (e.g. Me)", value: profileName, onChange: (e) => setProfileName(e.target.value) }),
855
+ /* @__PURE__ */ jsx("button", { onClick: () => {
856
+ if (profileName.trim()) saveProfile(profileName.trim());
857
+ }, children: "Save" })
858
+ ] }),
859
+ profileSaved && /* @__PURE__ */ jsxs("div", { className: "ps-tryon-save-done", children: [
860
+ /* @__PURE__ */ jsx(CheckIcon, {}),
861
+ " Profile saved"
862
+ ] })
863
+ ] })
864
+ ] });
865
+ }
866
+ function ErrorView() {
867
+ return /* @__PURE__ */ jsxs("div", { className: cx("ps-tryon-error", cn.error), children: [
868
+ /* @__PURE__ */ jsx(AlertIcon, {}),
869
+ /* @__PURE__ */ jsx("p", { className: cx("ps-tryon-error-text", cn.errorText), children: errorMessage || "Something went wrong" }),
870
+ /* @__PURE__ */ jsx("button", { onClick: handleRetry, className: cx("ps-tryon-submit", cn.submitButton), children: "Try Again" })
871
+ ] });
872
+ }
873
+ function DrawerPanel() {
874
+ if (!drawer) return null;
875
+ return /* @__PURE__ */ jsxs("div", { className: `ps-tryon-drawer${drawer ? " ps-tryon-drawer-open" : ""}`, children: [
876
+ /* @__PURE__ */ jsxs("div", { className: "ps-tryon-drawer-header", children: [
877
+ /* @__PURE__ */ jsx("button", { className: "ps-tryon-drawer-back", onClick: () => setDrawer(null), children: /* @__PURE__ */ jsx(ArrowLeftIcon, {}) }),
878
+ /* @__PURE__ */ jsx("span", { className: "ps-tryon-drawer-title", children: drawer === "profiles" ? "My Profiles" : "History" })
879
+ ] }),
880
+ /* @__PURE__ */ jsx("div", { className: "ps-tryon-drawer-list", children: drawer === "profiles" ? profiles.length === 0 ? /* @__PURE__ */ jsx("div", { className: "ps-tryon-drawer-empty", children: "No saved profiles yet." }) : profiles.map((p) => /* @__PURE__ */ jsxs("div", { className: "ps-tryon-profile-item", onClick: () => {
881
+ setProfileDetail(p);
882
+ setDrawer(null);
883
+ }, children: [
884
+ /* @__PURE__ */ jsx("div", { className: "ps-tryon-profile-avatar", children: /* @__PURE__ */ jsx(UserIcon, { size: 20 }) }),
885
+ /* @__PURE__ */ jsxs("div", { className: "ps-tryon-profile-info", children: [
886
+ /* @__PURE__ */ jsx("div", { className: "ps-tryon-profile-name", children: p.name }),
887
+ /* @__PURE__ */ jsxs("div", { className: "ps-tryon-profile-detail", children: [
888
+ p.gender === "female" ? "Women's" : "Men's",
889
+ p.heightCm ? ` · ${Math.round(p.heightCm)}cm` : ""
890
+ ] })
891
+ ] }),
892
+ /* @__PURE__ */ jsx(ChevronRightIcon, {})
893
+ ] }, p.id)) : history.length === 0 ? /* @__PURE__ */ jsx("div", { className: "ps-tryon-drawer-empty", children: "No history yet." }) : history.map((entry, idx) => /* @__PURE__ */ jsxs("div", { className: "ps-tryon-history-item", children: [
894
+ entry.productImage && /* @__PURE__ */ jsx("img", { src: entry.productImage, alt: "", className: "ps-tryon-history-thumb" }),
895
+ /* @__PURE__ */ jsxs("div", { className: "ps-tryon-history-info", children: [
896
+ /* @__PURE__ */ jsx("div", { className: "ps-tryon-history-product", children: entry.productTitle || "Product" }),
897
+ /* @__PURE__ */ jsxs("div", { className: "ps-tryon-history-meta", children: [
898
+ entry.profileName ? `${entry.profileName} · ` : "",
899
+ new Date(entry.date).toLocaleDateString()
900
+ ] }),
901
+ entry.recommendedSize && /* @__PURE__ */ jsx("div", { className: "ps-tryon-history-sizing", children: /* @__PURE__ */ jsx("span", { className: "ps-tryon-history-size-badge", children: entry.recommendedSize }) })
902
+ ] }),
903
+ entry.resultImageUrl && /* @__PURE__ */ jsx("img", { src: entry.resultImageUrl, alt: "", className: "ps-tryon-history-result-img" }),
904
+ /* @__PURE__ */ jsx("button", { className: "ps-tryon-history-delete", onClick: (e) => {
905
+ e.stopPropagation();
906
+ setHistory((prev) => prev.filter((_, i) => i !== idx));
907
+ }, children: /* @__PURE__ */ jsx(TrashIcon, {}) })
908
+ ] }, entry.id)) })
909
+ ] });
910
+ }
911
+ function ProfileDetailModal() {
912
+ if (!profileDetail) return null;
913
+ const p = profileDetail;
914
+ const measurements = [
915
+ { label: "Height", value: p.heightCm, unit: "cm" },
916
+ { label: "Weight", value: p.weightKg, unit: "kg" },
917
+ { label: "Chest", value: p.chest, unit: "cm" },
918
+ { label: "Bust", value: p.bust, unit: "cm" },
919
+ { label: "Waist", value: p.waist, unit: "cm" },
920
+ { label: "Hips", value: p.hips, unit: "cm" },
921
+ { label: "Shoulders", value: p.shoulderWidth, unit: "cm" },
922
+ { label: "Sleeve", value: p.sleeveLength, unit: "cm" },
923
+ { label: "Inseam", value: p.inseam, unit: "cm" },
924
+ { label: "Neck", value: p.neckCircumference, unit: "cm" },
925
+ { label: "Foot", value: p.footLengthCm, unit: "cm" },
926
+ { label: "Shoe EU", value: p.shoeEU ? Number(p.shoeEU) : void 0, unit: "" },
927
+ { label: "Shoe US", value: p.shoeUS ? Number(p.shoeUS) : void 0, unit: "" },
928
+ { label: "Shoe UK", value: p.shoeUK ? Number(p.shoeUK) : void 0, unit: "" }
929
+ ].filter((m) => m.value);
930
+ return /* @__PURE__ */ jsx("div", { className: "ps-tryon-detail-overlay", onClick: (e) => {
931
+ if (e.target === e.currentTarget) setProfileDetail(null);
932
+ }, children: /* @__PURE__ */ jsxs("div", { className: "ps-tryon-detail-modal", children: [
933
+ /* @__PURE__ */ jsxs("div", { className: "ps-tryon-detail-header", children: [
934
+ /* @__PURE__ */ jsx("span", { children: p.name }),
935
+ /* @__PURE__ */ jsx("button", { onClick: () => setProfileDetail(null), children: /* @__PURE__ */ jsx(XIcon, { size: 18 }) })
936
+ ] }),
937
+ /* @__PURE__ */ jsxs("div", { className: "ps-tryon-detail-body", children: [
938
+ /* @__PURE__ */ jsxs("div", { className: "ps-tryon-detail-gender", children: [
939
+ /* @__PURE__ */ jsx(UserIcon, { size: 18 }),
940
+ " ",
941
+ p.gender === "female" ? "Women's" : "Men's",
942
+ " Profile"
943
+ ] }),
944
+ measurements.length > 0 && /* @__PURE__ */ jsx("div", { className: "ps-tryon-detail-grid", children: measurements.map((m) => /* @__PURE__ */ jsxs("div", { className: "ps-tryon-detail-cell", children: [
945
+ /* @__PURE__ */ jsx("div", { className: "ps-tryon-detail-cell-label", children: m.label }),
946
+ /* @__PURE__ */ jsxs("div", { className: "ps-tryon-detail-cell-value", children: [
947
+ Math.round(m.value),
948
+ m.unit ? ` ${m.unit}` : ""
949
+ ] })
950
+ ] }, m.label)) }),
951
+ p.createdAt && /* @__PURE__ */ jsxs("div", { className: "ps-tryon-detail-date", children: [
952
+ "Saved ",
953
+ new Date(p.createdAt).toLocaleDateString()
954
+ ] }),
955
+ /* @__PURE__ */ jsxs("button", { className: "ps-tryon-detail-delete", onClick: () => {
956
+ setProfiles((prev) => prev.filter((x) => x.id !== p.id));
957
+ if (activeProfileId === p.id) setActiveProfileId(null);
958
+ setProfileDetail(null);
959
+ }, children: [
960
+ /* @__PURE__ */ jsx(TrashIcon, {}),
961
+ " Delete Profile"
962
+ ] })
963
+ ] })
964
+ ] }) });
965
+ }
966
+ function renderBody() {
967
+ switch (view) {
968
+ case "welcome":
969
+ return /* @__PURE__ */ jsx(WelcomeView, {});
970
+ case "upload":
971
+ return /* @__PURE__ */ jsx(UploadView, {});
972
+ case "sizing-choice":
973
+ return /* @__PURE__ */ jsx(SizingChoiceView, {});
974
+ case "sizing-form":
975
+ return /* @__PURE__ */ jsx(SizingFormView, {});
976
+ case "processing":
977
+ return /* @__PURE__ */ jsx(ProcessingView, {});
978
+ case "result":
979
+ return /* @__PURE__ */ jsx(ResultView, {});
980
+ case "error":
981
+ return /* @__PURE__ */ jsx(ErrorView, {});
982
+ default:
983
+ return null;
984
+ }
985
+ }
986
+ return /* @__PURE__ */ jsxs("div", { className: cx("ps-tryon-root", cn.root || className), style: { ...cssVars, ...style }, "data-ps-tryon": true, children: [
987
+ /* @__PURE__ */ jsxs("button", { onClick: handleOpen, className: cx("ps-tryon-btn", cn.button), children: [
988
+ /* @__PURE__ */ jsx(CameraIcon, { size: parseInt(btnS.iconSize || "18") }),
989
+ /* @__PURE__ */ jsx("span", { children: buttonText })
990
+ ] }),
991
+ view !== "idle" && /* @__PURE__ */ jsx("div", { className: cx("ps-tryon-overlay", cn.overlay), onClick: (e) => {
992
+ if (e.target === e.currentTarget) handleClose();
993
+ }, children: /* @__PURE__ */ jsxs("div", { className: cx(`ps-tryon-modal${view === "result" && resultImageUrl && sizingResult ? " ps-tryon-modal-wide" : ""}`, cn.modal), onClick: (e) => e.stopPropagation(), children: [
994
+ /* @__PURE__ */ jsxs("div", { className: cx("ps-tryon-header", cn.header), children: [
995
+ /* @__PURE__ */ jsx("span", { className: cx("ps-tryon-title", cn.title), children: "Virtual Try-On" }),
996
+ /* @__PURE__ */ jsxs("div", { className: "ps-tryon-header-actions", children: [
997
+ profiles.length > 0 && /* @__PURE__ */ jsx("button", { className: "ps-tryon-header-icon", title: "Profiles", onClick: () => setDrawer(drawer === "profiles" ? null : "profiles"), children: /* @__PURE__ */ jsx(UserIcon, {}) }),
998
+ history.length > 0 && /* @__PURE__ */ jsx("button", { className: "ps-tryon-header-icon", title: "History", onClick: () => setDrawer(drawer === "history" ? null : "history"), children: /* @__PURE__ */ jsx(ClockIcon, {}) }),
999
+ /* @__PURE__ */ jsx("button", { onClick: handleClose, className: cx("ps-tryon-close", cn.closeButton), children: /* @__PURE__ */ jsx(XIcon, {}) })
1000
+ ] })
1001
+ ] }),
1002
+ /* @__PURE__ */ jsx(Stepper, {}),
1003
+ /* @__PURE__ */ jsxs("div", { ref: bodyRef, className: cx("ps-tryon-body", cn.body), style: { position: "relative", overflow: drawer ? "hidden" : void 0 }, children: [
1004
+ renderBody(),
1005
+ /* @__PURE__ */ jsx(DrawerPanel, {})
1006
+ ] }),
1007
+ showPoweredBy && /* @__PURE__ */ jsxs("div", { className: cx("ps-tryon-powered", cn.poweredBy), children: [
1008
+ "Powered by",
1009
+ " ",
1010
+ /* @__PURE__ */ jsx("a", { href: "https://myaifitting.com", target: "_blank", rel: "noopener noreferrer", children: "PrimeStyle AI" })
1011
+ ] })
1012
+ ] }) }),
1013
+ /* @__PURE__ */ jsx(ProfileDetailModal, {}),
1014
+ /* @__PURE__ */ jsx("style", { children: STYLES })
1015
+ ] });
1016
+ }
1017
+ const STYLES = `
1018
+ .ps-tryon-root { display: inline-block; }
608
1019
 
609
- .ps-tryon-btn {
610
- display: inline-flex;
611
- align-items: center;
612
- gap: 8px;
613
- padding: var(--ps-btn-padding, 12px 24px);
614
- background: var(--ps-btn-bg, #bb945c);
615
- color: var(--ps-btn-color, #111211);
616
- font-family: var(--ps-btn-font, system-ui, -apple-system, sans-serif);
617
- font-size: var(--ps-btn-font-size, 14px);
618
- font-weight: var(--ps-btn-font-weight, 600);
619
- border: var(--ps-btn-border, none);
620
- border-radius: var(--ps-btn-radius, 8px);
621
- cursor: pointer;
622
- transition: all 0.2s ease;
623
- width: var(--ps-btn-width, auto);
624
- height: var(--ps-btn-height, auto);
625
- box-shadow: var(--ps-btn-shadow, none);
626
- line-height: 1;
627
- white-space: nowrap;
628
- }
629
- .ps-tryon-btn:hover {
630
- background: var(--ps-btn-hover-bg, #a07d4e);
631
- color: var(--ps-btn-hover-color, var(--ps-btn-color, #111211));
632
- transform: translateY(-1px);
633
- }
634
- .ps-tryon-btn:active { transform: translateY(0); }
1020
+ .ps-tryon-btn {
1021
+ display: inline-flex; align-items: center; gap: 8px;
1022
+ padding: var(--ps-btn-padding, 12px 24px);
1023
+ background: var(--ps-btn-bg, #bb945c); color: var(--ps-btn-color, #111211);
1024
+ font-family: var(--ps-btn-font, system-ui, -apple-system, sans-serif);
1025
+ font-size: var(--ps-btn-font-size, 14px); font-weight: var(--ps-btn-font-weight, 600);
1026
+ border: var(--ps-btn-border, none); border-radius: var(--ps-btn-radius, 8px);
1027
+ cursor: pointer; transition: all 0.2s ease;
1028
+ width: var(--ps-btn-width, auto); height: var(--ps-btn-height, auto);
1029
+ box-shadow: var(--ps-btn-shadow, none); line-height: 1; white-space: nowrap;
1030
+ }
1031
+ .ps-tryon-btn:hover { background: var(--ps-btn-hover-bg, #a07d4e); transform: translateY(-1px); }
1032
+ .ps-tryon-btn:active { transform: translateY(0); }
635
1033
 
636
- .ps-tryon-overlay {
637
- position: fixed;
638
- inset: 0;
639
- background: var(--ps-modal-overlay, rgba(0,0,0,0.6));
640
- display: flex;
641
- align-items: center;
642
- justify-content: center;
643
- z-index: 999999;
644
- padding: 16px;
645
- animation: ps-fade-in 0.2s ease;
646
- }
647
- @keyframes ps-fade-in {
648
- from { opacity: 0; }
649
- to { opacity: 1; }
650
- }
1034
+ .ps-tryon-overlay {
1035
+ position: fixed; inset: 0; background: var(--ps-modal-overlay, rgba(0,0,0,0.6));
1036
+ display: flex; align-items: center; justify-content: center;
1037
+ z-index: 999999; padding: 16px; animation: ps-fade-in 0.2s ease;
1038
+ }
1039
+ @keyframes ps-fade-in { from { opacity: 0; } to { opacity: 1; } }
651
1040
 
652
- .ps-tryon-modal {
653
- background: var(--ps-modal-bg, #111211);
654
- color: var(--ps-modal-color, #ffffff);
655
- border-radius: var(--ps-modal-radius, 12px);
656
- width: var(--ps-modal-width, 100%);
657
- max-width: var(--ps-modal-max-width, 480px);
658
- max-height: 90vh;
659
- overflow-y: auto;
660
- font-family: var(--ps-modal-font, system-ui, -apple-system, sans-serif);
661
- box-shadow: 0 25px 50px rgba(0,0,0,0.4);
662
- animation: ps-slide-up 0.25s ease;
663
- }
664
- @keyframes ps-slide-up {
665
- from { transform: translateY(20px) scale(0.97); opacity: 0; }
666
- to { transform: translateY(0) scale(1); opacity: 1; }
667
- }
1041
+ .ps-tryon-modal {
1042
+ background: var(--ps-modal-bg, #111211); color: var(--ps-modal-color, #fff);
1043
+ border-radius: var(--ps-modal-radius, 16px); width: var(--ps-modal-width, 100%);
1044
+ max-width: var(--ps-modal-max-width, 520px); max-height: 92vh; overflow-y: auto;
1045
+ font-family: var(--ps-modal-font, system-ui, -apple-system, sans-serif);
1046
+ box-shadow: 0 25px 50px rgba(0,0,0,0.4); animation: ps-slide-up 0.3s ease;
1047
+ scrollbar-width: thin; scrollbar-color: #333 transparent;
1048
+ }
1049
+ .ps-tryon-modal-wide { max-width: 920px; }
1050
+ @keyframes ps-slide-up { from { transform: translateY(20px) scale(0.97); opacity: 0; } to { transform: translateY(0) scale(1); opacity: 1; } }
668
1051
 
669
- .ps-tryon-header {
670
- display: flex;
671
- align-items: center;
672
- justify-content: space-between;
673
- padding: 20px 24px;
674
- background: var(--ps-modal-header-bg, #1a1b1a);
675
- border-bottom: 1px solid #333;
676
- border-radius: var(--ps-modal-radius, 12px) var(--ps-modal-radius, 12px) 0 0;
677
- }
678
- .ps-tryon-title {
679
- font-size: 16px;
680
- font-weight: 600;
681
- color: var(--ps-modal-header-color, #fff);
682
- }
683
- .ps-tryon-close {
684
- width: 32px;
685
- height: 32px;
686
- display: flex;
687
- align-items: center;
688
- justify-content: center;
689
- background: none;
690
- border: none;
691
- color: var(--ps-modal-close-color, #999);
692
- cursor: pointer;
693
- border-radius: 6px;
694
- transition: background 0.15s;
695
- }
696
- .ps-tryon-close:hover { background: rgba(255,255,255,0.1); }
1052
+ /* Header */
1053
+ .ps-tryon-header {
1054
+ display: flex; align-items: center; justify-content: space-between;
1055
+ padding: 18px 24px; background: var(--ps-modal-header-bg, #1a1b1a);
1056
+ border-bottom: 1px solid #333;
1057
+ border-radius: var(--ps-modal-radius, 16px) var(--ps-modal-radius, 16px) 0 0;
1058
+ }
1059
+ .ps-tryon-title { font-size: 15px; font-weight: 600; color: var(--ps-modal-header-color, #fff); }
1060
+ .ps-tryon-header-actions { display: flex; align-items: center; gap: 8px; }
1061
+ .ps-tryon-header-icon {
1062
+ width: 34px; height: 34px; display: flex; align-items: center; justify-content: center;
1063
+ border: 1.5px solid #333; border-radius: 10px; background: transparent;
1064
+ cursor: pointer; color: #999; transition: all 0.2s;
1065
+ }
1066
+ .ps-tryon-header-icon:hover { border-color: #bb945c; color: #bb945c; }
1067
+ .ps-tryon-header-icon svg { stroke: currentColor; fill: none; }
1068
+ .ps-tryon-close {
1069
+ width: 32px; height: 32px; display: flex; align-items: center; justify-content: center;
1070
+ background: none; border: none; color: var(--ps-modal-close-color, #999);
1071
+ cursor: pointer; border-radius: 6px; transition: background 0.15s;
1072
+ }
1073
+ .ps-tryon-close:hover { background: rgba(255,255,255,0.1); }
697
1074
 
698
- .ps-tryon-body { padding: 24px; }
1075
+ /* Stepper */
1076
+ .ps-tryon-stepper { padding: 20px 24px 12px; }
1077
+ .ps-tryon-stepper-track { display: flex; align-items: flex-start; }
1078
+ .ps-tryon-stepper-fragment { display: flex; align-items: flex-start; flex: 1; }
1079
+ .ps-tryon-stepper-fragment:first-child { flex: 0 0 auto; }
1080
+ .ps-tryon-stepper-line { flex: 1; height: 2px; background: #333; margin-top: 14px; transition: background 0.4s; }
1081
+ .ps-tryon-stepper-line.ps-done { background: #bb945c; }
1082
+ .ps-tryon-stepper-step { display: flex; flex-direction: column; align-items: center; }
1083
+ .ps-tryon-stepper-circle {
1084
+ width: 28px; height: 28px; border-radius: 50%;
1085
+ display: flex; align-items: center; justify-content: center;
1086
+ border: 2px solid #333; font-size: 12px; font-weight: 600; color: #666;
1087
+ transition: all 0.3s;
1088
+ }
1089
+ .ps-tryon-stepper-step.ps-done .ps-tryon-stepper-circle { background: #bb945c; border-color: #bb945c; color: #111; }
1090
+ .ps-tryon-stepper-step.ps-done .ps-tryon-stepper-circle svg { stroke: #111; }
1091
+ .ps-tryon-stepper-step.ps-active .ps-tryon-stepper-circle { border-color: #bb945c; color: #bb945c; box-shadow: 0 0 0 4px rgba(187,148,92,0.15); }
1092
+ .ps-tryon-stepper-label { font-size: 10px; margin-top: 5px; color: #666; font-weight: 500; text-align: center; white-space: nowrap; }
1093
+ .ps-tryon-stepper-step.ps-done .ps-tryon-stepper-label { color: #bb945c; }
1094
+ .ps-tryon-stepper-step.ps-active .ps-tryon-stepper-label { color: #bb945c; font-weight: 700; }
699
1095
 
700
- .ps-tryon-upload {
701
- border: 2px dashed var(--ps-upload-border, #333);
702
- border-radius: 12px;
703
- padding: 40px 24px;
704
- text-align: center;
705
- cursor: pointer;
706
- transition: all 0.2s;
707
- background: var(--ps-upload-bg, transparent);
708
- display: flex;
709
- flex-direction: column;
710
- align-items: center;
711
- }
712
- .ps-tryon-upload:hover, .ps-tryon-drag-over {
713
- border-color: #bb945c;
714
- background: rgba(187,148,92,0.05);
715
- }
716
- .ps-tryon-upload svg {
717
- color: var(--ps-upload-icon-color, #bb945c);
718
- margin-bottom: 12px;
719
- }
720
- .ps-tryon-upload-text {
721
- font-size: 14px;
722
- color: var(--ps-upload-color, #fff);
723
- margin: 0 0 4px;
724
- }
725
- .ps-tryon-upload-hint {
726
- font-size: 12px;
727
- color: #999;
728
- margin: 0;
729
- }
1096
+ /* Body */
1097
+ .ps-tryon-body { padding: 24px; min-height: 300px; }
1098
+ .ps-tryon-body > * { animation: ps-fade-up 0.35s ease both; }
1099
+ @keyframes ps-fade-up { from { opacity: 0; transform: translateY(12px); } to { opacity: 1; transform: translateY(0); } }
730
1100
 
731
- .ps-tryon-preview {
732
- position: relative;
733
- margin-bottom: 4px;
734
- }
735
- .ps-tryon-preview img {
736
- width: 100%;
737
- border-radius: 12px;
738
- display: block;
739
- }
740
- .ps-tryon-preview-remove {
741
- position: absolute;
742
- top: 8px;
743
- right: 8px;
744
- width: 28px;
745
- height: 28px;
746
- border-radius: 50%;
747
- background: rgba(0,0,0,0.6);
748
- border: none;
749
- color: white;
750
- cursor: pointer;
751
- display: flex;
752
- align-items: center;
753
- justify-content: center;
754
- font-size: 16px;
755
- transition: background 0.15s;
756
- }
757
- .ps-tryon-preview-remove:hover { background: rgba(0,0,0,0.8); }
1101
+ /* Welcome */
1102
+ .ps-tryon-welcome { text-align: center; }
1103
+ .ps-tryon-welcome-hero { margin-bottom: 20px; }
1104
+ .ps-tryon-welcome-img-wrap { position: relative; display: inline-block; }
1105
+ .ps-tryon-welcome-product { width: 120px; height: 140px; object-fit: cover; border-radius: 14px; border: 2px solid #333; }
1106
+ .ps-tryon-welcome-sparkle { position: absolute; top: -8px; right: -8px; width: 28px; height: 28px; background: #bb945c; border-radius: 50%; display: flex; align-items: center; justify-content: center; animation: ps-float 3s ease-in-out infinite; }
1107
+ .ps-tryon-welcome-sparkle svg { stroke: #111; width: 14px; height: 14px; }
1108
+ @keyframes ps-float { 0%,100% { transform: translateY(0); } 50% { transform: translateY(-6px); } }
1109
+ .ps-tryon-welcome-title { font-size: 20px; font-weight: 700; color: #fff; margin: 14px 0 4px; }
1110
+ .ps-tryon-welcome-sub { font-size: 13px; color: #999; margin: 0; }
1111
+ .ps-tryon-features { display: flex; gap: 10px; margin: 20px 0; }
1112
+ .ps-tryon-feature { flex: 1; padding: 14px 10px; border: 1px solid #333; border-radius: 12px; text-align: center; }
1113
+ .ps-tryon-feature-icon { margin-bottom: 6px; color: #bb945c; display: flex; justify-content: center; }
1114
+ .ps-tryon-feature-icon svg { stroke: currentColor; fill: none; }
1115
+ .ps-tryon-feature-title { font-size: 12px; font-weight: 600; color: #fff; margin-bottom: 2px; }
1116
+ .ps-tryon-feature-desc { font-size: 10px; color: #999; }
1117
+ .ps-tryon-cta {
1118
+ width: 100%; padding: 14px; background: #bb945c; color: #111; border: none;
1119
+ border-radius: 12px; font-size: 15px; font-weight: 700; cursor: pointer;
1120
+ display: flex; align-items: center; justify-content: center; gap: 8px; transition: all 0.2s;
1121
+ font-family: var(--ps-modal-font, system-ui, sans-serif);
1122
+ }
1123
+ .ps-tryon-cta:hover { background: #a07d4e; transform: translateY(-1px); }
1124
+ .ps-tryon-cta svg { stroke: #111; }
1125
+ .ps-tryon-welcome-note { font-size: 11px; color: #666; margin-top: 12px; }
758
1126
 
759
- .ps-tryon-submit {
760
- width: 100%;
761
- padding: 14px;
762
- margin-top: 20px;
763
- background: var(--ps-modal-primary-bg, #bb945c);
764
- color: var(--ps-modal-primary-color, #111211);
765
- font-family: var(--ps-modal-font, system-ui, sans-serif);
766
- font-size: 14px;
767
- font-weight: 600;
768
- border: none;
769
- border-radius: var(--ps-modal-primary-radius, 8px);
770
- cursor: pointer;
771
- transition: all 0.2s;
772
- display: flex;
773
- align-items: center;
774
- justify-content: center;
775
- gap: 8px;
776
- }
777
- .ps-tryon-submit:hover { opacity: 0.9; transform: translateY(-1px); }
778
- .ps-tryon-submit:disabled { opacity: 0.5; cursor: not-allowed; }
1127
+ /* Upload */
1128
+ .ps-tryon-upload {
1129
+ border: 2px dashed var(--ps-upload-border, #333); border-radius: 12px;
1130
+ padding: 40px 24px; text-align: center; cursor: pointer; transition: all 0.2s;
1131
+ background: var(--ps-upload-bg, transparent); display: flex; flex-direction: column; align-items: center;
1132
+ }
1133
+ .ps-tryon-upload:hover, .ps-tryon-drag-over { border-color: #bb945c; background: rgba(187,148,92,0.05); }
1134
+ .ps-tryon-upload svg { color: var(--ps-upload-icon-color, #bb945c); margin-bottom: 12px; }
1135
+ .ps-tryon-upload-text { font-size: 14px; color: var(--ps-upload-color, #fff); margin: 0 0 4px; }
1136
+ .ps-tryon-upload-hint { font-size: 12px; color: #999; margin: 0; }
1137
+ .ps-tryon-preview { position: relative; margin-bottom: 4px; }
1138
+ .ps-tryon-preview img { width: 100%; border-radius: 12px; display: block; }
1139
+ .ps-tryon-preview-remove {
1140
+ position: absolute; top: 8px; right: 8px; width: 28px; height: 28px;
1141
+ border-radius: 50%; background: rgba(0,0,0,0.6); border: none; color: white;
1142
+ cursor: pointer; display: flex; align-items: center; justify-content: center;
1143
+ font-size: 16px; transition: background 0.15s;
1144
+ }
1145
+ .ps-tryon-preview-remove:hover { background: rgba(0,0,0,0.8); }
1146
+ .ps-tryon-submit {
1147
+ width: 100%; padding: 14px; margin-top: 16px;
1148
+ background: var(--ps-modal-primary-bg, #bb945c); color: var(--ps-modal-primary-color, #111);
1149
+ font-family: var(--ps-modal-font, system-ui, sans-serif);
1150
+ font-size: 14px; font-weight: 600; border: none;
1151
+ border-radius: var(--ps-modal-primary-radius, 10px);
1152
+ cursor: pointer; transition: all 0.2s;
1153
+ display: flex; align-items: center; justify-content: center; gap: 8px;
1154
+ }
1155
+ .ps-tryon-submit:hover { opacity: 0.9; transform: translateY(-1px); }
1156
+ .ps-tryon-submit svg { stroke: currentColor; }
779
1157
 
780
- .ps-tryon-processing {
781
- text-align: center;
782
- padding: 40px 24px;
783
- }
784
- .ps-tryon-spinner {
785
- width: 48px;
786
- height: 48px;
787
- border: 3px solid #333;
788
- border-top-color: var(--ps-loader, #bb945c);
789
- border-radius: 50%;
790
- animation: ps-spin 0.8s linear infinite;
791
- margin: 0 auto 16px;
792
- }
793
- @keyframes ps-spin { to { transform: rotate(360deg); } }
1158
+ /* Profile bar */
1159
+ .ps-tryon-profile-bar { margin-bottom: 16px; }
1160
+ .ps-tryon-profile-select {
1161
+ width: 100%; padding: 10px 36px 10px 14px; border: 1.5px solid #333; border-radius: 10px;
1162
+ background: #1a1b1a; color: #fff; font-size: 13px; font-family: inherit;
1163
+ appearance: none; -webkit-appearance: none; cursor: pointer; outline: none;
1164
+ background-image: url("data:image/svg+xml,%3Csvg width='12' height='12' viewBox='0 0 12 12' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M2.5 4.5L6 8L9.5 4.5' stroke='%23999' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
1165
+ background-repeat: no-repeat; background-position: right 14px center;
1166
+ }
1167
+ .ps-tryon-profile-select:focus { border-color: #bb945c; }
794
1168
 
795
- .ps-tryon-countdown-ring {
796
- position: relative;
797
- width: 100px;
798
- height: 100px;
799
- margin: 0 auto 20px;
800
- }
801
- .ps-tryon-countdown-ring svg {
802
- width: 100%;
803
- height: 100%;
804
- transform: rotate(-90deg);
805
- }
806
- .ps-tryon-countdown-track {
807
- fill: none;
808
- stroke: #333;
809
- stroke-width: 4;
810
- }
811
- .ps-tryon-countdown-progress {
812
- fill: none;
813
- stroke: var(--ps-loader, #bb945c);
814
- stroke-width: 4;
815
- stroke-linecap: round;
816
- stroke-dasharray: 326.73;
817
- transition: stroke-dashoffset 1s linear;
818
- }
819
- .ps-tryon-countdown-number {
820
- position: absolute;
821
- inset: 0;
822
- display: flex;
823
- align-items: center;
824
- justify-content: center;
825
- font-size: 28px;
826
- font-weight: 700;
827
- color: #fff;
828
- font-variant-numeric: tabular-nums;
829
- }
1169
+ /* Sizing choice */
1170
+ .ps-tryon-sizing-choice { text-align: center; }
1171
+ .ps-tryon-section-title { font-size: 16px; font-weight: 600; color: #fff; margin: 0 0 20px; }
1172
+ .ps-tryon-choice-cards { display: flex; flex-direction: column; gap: 12px; }
1173
+ .ps-tryon-choice-card {
1174
+ padding: 20px; border: 1.5px solid #333; border-radius: 14px;
1175
+ background: #1a1b1a; cursor: pointer; transition: all 0.25s; text-align: left;
1176
+ display: block; width: 100%; font-family: inherit;
1177
+ }
1178
+ .ps-tryon-choice-card:hover { border-color: #bb945c; box-shadow: 0 4px 20px rgba(187,148,92,0.08); }
1179
+ .ps-tryon-choice-icon { color: #bb945c; margin-bottom: 8px; }
1180
+ .ps-tryon-choice-icon svg { stroke: currentColor; fill: none; }
1181
+ .ps-tryon-choice-title { font-size: 15px; font-weight: 600; color: #fff; margin-bottom: 4px; }
1182
+ .ps-tryon-choice-desc { font-size: 12px; color: #999; }
1183
+ .ps-tryon-choice-badge {
1184
+ display: inline-block; margin-top: 8px; padding: 3px 10px; border-radius: 20px;
1185
+ background: rgba(187,148,92,0.12); color: #bb945c; font-size: 11px; font-weight: 600;
1186
+ }
1187
+ .ps-tryon-skip-btn {
1188
+ margin-top: 16px; padding: 10px; background: none; border: 1px solid #333;
1189
+ border-radius: 10px; color: #999; font-size: 13px; cursor: pointer; transition: all 0.2s;
1190
+ width: 100%; font-family: inherit;
1191
+ }
1192
+ .ps-tryon-skip-btn:hover { border-color: #666; color: #ccc; }
830
1193
 
831
- .ps-tryon-done-pulse {
832
- animation: ps-fade-scale-in 0.4s ease both;
833
- margin-bottom: 4px;
834
- }
835
- @keyframes ps-fade-scale-in {
836
- from { opacity: 0; transform: scale(0.8); }
837
- to { opacity: 1; transform: scale(1); }
838
- }
1194
+ /* Sizing form */
1195
+ .ps-tryon-sizing-form { display: flex; flex-direction: column; gap: 12px; }
1196
+ .ps-tryon-input-row { display: flex; align-items: center; gap: 10px; }
1197
+ .ps-tryon-input-row label { font-size: 13px; font-weight: 500; color: #ccc; min-width: 90px; flex-shrink: 0; }
1198
+ .ps-tryon-input-row input {
1199
+ flex: 1; padding: 10px 14px; border: 1.5px solid #333; border-radius: 10px;
1200
+ background: #1a1b1a; color: #fff; font-size: 14px; font-family: inherit; outline: none;
1201
+ transition: border-color 0.2s; -moz-appearance: textfield;
1202
+ }
1203
+ .ps-tryon-input-row input::-webkit-outer-spin-button,
1204
+ .ps-tryon-input-row input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
1205
+ .ps-tryon-input-row input:focus { border-color: #bb945c; }
1206
+ .ps-tryon-input-unit { font-size: 12px; color: #666; flex-shrink: 0; }
1207
+ .ps-tryon-dual-input { gap: 8px; }
1208
+ .ps-tryon-dual-input input { width: 50%; }
1209
+ .ps-tryon-country-select {
1210
+ flex: 1; padding: 10px 36px 10px 14px; border: 1.5px solid #333; border-radius: 10px;
1211
+ background: #1a1b1a; color: #fff; font-size: 13px; font-family: inherit;
1212
+ appearance: none; -webkit-appearance: none; cursor: pointer; outline: none;
1213
+ background-image: url("data:image/svg+xml,%3Csvg width='12' height='12' viewBox='0 0 12 12' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M2.5 4.5L6 8L9.5 4.5' stroke='%23999' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
1214
+ background-repeat: no-repeat; background-position: right 14px center;
1215
+ }
1216
+ .ps-tryon-country-select:focus { border-color: #bb945c; }
1217
+ .ps-tryon-unit-toggle { display: flex; gap: 0; border: 1.5px solid #333; border-radius: 8px; overflow: hidden; }
1218
+ .ps-tryon-unit-btn {
1219
+ padding: 6px 14px; background: transparent; border: none; color: #999;
1220
+ font-size: 12px; font-weight: 600; cursor: pointer; transition: all 0.2s; font-family: inherit;
1221
+ }
1222
+ .ps-tryon-unit-btn.ps-active { background: #bb945c; color: #111; }
1223
+ .ps-tryon-shoe-section { border-top: 1px solid #333; padding-top: 14px; margin-top: 6px; display: flex; flex-direction: column; gap: 10px; }
1224
+ .ps-tryon-shoe-title { font-size: 13px; font-weight: 600; color: #999; }
1225
+ .ps-tryon-disclaimer { font-size: 11px; color: #666; margin: 4px 0 0; }
839
1226
 
840
- .ps-tryon-processing-text { font-size: 14px; color: #fff; margin: 0 0 4px; }
841
- .ps-tryon-processing-sub { font-size: 12px; color: #999; margin: 0; }
1227
+ /* Processing */
1228
+ .ps-tryon-processing { text-align: center; padding: 40px 24px; }
1229
+ .ps-tryon-spinner {
1230
+ width: 48px; height: 48px; border: 3px solid #333;
1231
+ border-top-color: var(--ps-loader, #bb945c); border-radius: 50%;
1232
+ animation: ps-spin 0.8s linear infinite; margin: 0 auto 16px;
1233
+ }
1234
+ @keyframes ps-spin { to { transform: rotate(360deg); } }
1235
+ .ps-tryon-countdown-ring { position: relative; width: 100px; height: 100px; margin: 0 auto 20px; }
1236
+ .ps-tryon-countdown-ring svg { width: 100%; height: 100%; transform: rotate(-90deg); }
1237
+ .ps-tryon-countdown-track { fill: none; stroke: #333; stroke-width: 4; }
1238
+ .ps-tryon-countdown-progress { fill: none; stroke: var(--ps-loader, #bb945c); stroke-width: 4; stroke-linecap: round; stroke-dasharray: 326.73; transition: stroke-dashoffset 1s linear; }
1239
+ .ps-tryon-countdown-number { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; font-size: 28px; font-weight: 700; color: #fff; font-variant-numeric: tabular-nums; }
1240
+ .ps-tryon-done-pulse { animation: ps-scale-in 0.4s ease both; }
1241
+ @keyframes ps-scale-in { from { opacity: 0; transform: scale(0.8); } to { opacity: 1; transform: scale(1); } }
1242
+ .ps-tryon-processing-text { font-size: 14px; color: #fff; margin: 0 0 4px; }
1243
+ .ps-tryon-processing-sub { font-size: 12px; color: #999; margin: 0; }
842
1244
 
843
- .ps-tryon-result { text-align: center; }
844
- .ps-tryon-result img {
845
- width: 100%;
846
- border-radius: var(--ps-result-radius, 12px);
847
- display: block;
848
- margin-bottom: 16px;
849
- }
850
- .ps-tryon-result-actions { display: flex; gap: 8px; }
851
- .ps-tryon-result-actions button {
852
- flex: 1;
853
- padding: 12px;
854
- font-family: var(--ps-modal-font, system-ui, sans-serif);
855
- font-size: 13px;
856
- font-weight: 600;
857
- border-radius: 8px;
858
- cursor: pointer;
859
- transition: all 0.2s;
860
- border: none;
861
- }
862
- .ps-tryon-btn-download { background: #bb945c; color: #111211; }
863
- .ps-tryon-btn-download:hover { opacity: 0.9; }
864
- .ps-tryon-btn-retry {
865
- background: rgba(255,255,255,0.1);
866
- color: #fff;
867
- border: 1px solid #333 !important;
868
- }
869
- .ps-tryon-btn-retry:hover { background: rgba(255,255,255,0.15); }
1245
+ /* Result */
1246
+ .ps-tryon-result-layout { text-align: center; }
1247
+ .ps-tryon-result-split { display: flex; gap: 24px; text-align: left; align-items: stretch; }
1248
+ .ps-tryon-result-image-col { flex: 0 0 45%; min-width: 0; display: flex; align-items: center; justify-content: center; }
1249
+ .ps-tryon-result-image-col img { width: 100%; max-height: 400px; object-fit: contain; border-radius: 14px; box-shadow: 0 8px 32px rgba(0,0,0,0.2); animation: ps-scale-in 0.5s ease both; }
1250
+ .ps-tryon-result-info-col { flex: 1; min-width: 0; }
1251
+ .ps-tryon-result-layout:not(.ps-tryon-result-split) img { width: 100%; border-radius: 12px; margin-bottom: 16px; }
1252
+ .ps-tryon-result-actions { display: flex; gap: 8px; margin-top: 16px; }
1253
+ .ps-tryon-result-actions button {
1254
+ flex: 1; padding: 12px; font-family: var(--ps-modal-font, system-ui, sans-serif);
1255
+ font-size: 13px; font-weight: 600; border-radius: 10px; cursor: pointer; transition: all 0.2s; border: none;
1256
+ }
1257
+ .ps-tryon-btn-download { background: #bb945c; color: #111; }
1258
+ .ps-tryon-btn-download:hover { opacity: 0.9; }
1259
+ .ps-tryon-btn-retry { background: rgba(255,255,255,0.08); color: #fff; border: 1px solid #333 !important; }
1260
+ .ps-tryon-btn-retry:hover { background: rgba(255,255,255,0.12); }
870
1261
 
871
- .ps-tryon-error {
872
- text-align: center;
873
- padding: 24px;
874
- display: flex;
875
- flex-direction: column;
876
- align-items: center;
877
- }
878
- .ps-tryon-error svg { color: #ef4444; margin-bottom: 12px; }
879
- .ps-tryon-error-text { font-size: 14px; color: #ef4444; margin: 0 0 16px; }
1262
+ /* Size recommendation */
1263
+ .ps-tryon-size-recommend { margin-bottom: 16px; }
1264
+ .ps-tryon-size-badge {
1265
+ display: inline-flex; align-items: center; justify-content: center;
1266
+ width: 56px; height: 56px; border-radius: 50%;
1267
+ background: linear-gradient(135deg, #bb945c, #d6ba7d);
1268
+ color: #111; font-size: 20px; font-weight: 700; margin-bottom: 10px;
1269
+ }
1270
+ .ps-tryon-size-confidence { font-size: 12px; color: #999; margin-bottom: 8px; }
1271
+ .ps-conf-high { color: #4ade80; } .ps-conf-medium { color: #bb945c; } .ps-conf-low { color: #ef4444; }
1272
+ .ps-tryon-size-reasoning { font-size: 13px; color: #ccc; line-height: 1.5; margin-bottom: 12px; }
1273
+ .ps-tryon-intl-sizes { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 12px; }
1274
+ .ps-tryon-intl-chip { padding: 4px 10px; border: 1px solid #333; border-radius: 8px; font-size: 11px; color: #ccc; font-weight: 500; }
1275
+ .ps-tryon-match-details { margin-top: 12px; }
1276
+ .ps-tryon-match-details summary { font-size: 12px; color: #bb945c; cursor: pointer; font-weight: 600; }
1277
+ .ps-tryon-match-details table { width: 100%; border-collapse: collapse; margin-top: 8px; font-size: 12px; }
1278
+ .ps-tryon-match-details th { text-align: left; padding: 6px 8px; border-bottom: 1px solid #333; color: #999; font-weight: 600; }
1279
+ .ps-tryon-match-details td { padding: 6px 8px; border-bottom: 1px solid #222; color: #ccc; }
1280
+ .ps-fit-good { color: #4ade80; } .ps-fit-tight { color: #f59e0b; } .ps-fit-loose { color: #60a5fa; }
880
1281
 
881
- .ps-tryon-powered {
882
- text-align: center;
883
- padding: 12px 24px 16px;
884
- font-size: 11px;
885
- color: #999;
886
- }
887
- .ps-tryon-powered a { color: #bb945c; text-decoration: none; }
888
- .ps-tryon-powered a:hover { text-decoration: underline; }
889
- ` })
890
- ]
891
- }
892
- );
893
- }
1282
+ /* Save profile prompt */
1283
+ .ps-tryon-save-prompt { display: flex; gap: 8px; margin-top: 14px; }
1284
+ .ps-tryon-save-prompt input {
1285
+ flex: 1; padding: 10px 14px; border: 1.5px solid #333; border-radius: 10px;
1286
+ background: #1a1b1a; color: #fff; font-size: 13px; font-family: inherit; outline: none;
1287
+ }
1288
+ .ps-tryon-save-prompt input:focus { border-color: #bb945c; }
1289
+ .ps-tryon-save-prompt button {
1290
+ padding: 10px 20px; background: #bb945c; color: #111; border: none; border-radius: 10px;
1291
+ font-size: 13px; font-weight: 600; cursor: pointer; transition: opacity 0.2s; font-family: inherit;
1292
+ }
1293
+ .ps-tryon-save-prompt button:hover { opacity: 0.9; }
1294
+ .ps-tryon-save-done { font-size: 12px; color: #4ade80; margin-top: 10px; display: flex; align-items: center; gap: 6px; justify-content: center; }
1295
+ .ps-tryon-save-done svg { stroke: currentColor; }
1296
+
1297
+ /* Error */
1298
+ .ps-tryon-error { text-align: center; padding: 24px; display: flex; flex-direction: column; align-items: center; }
1299
+ .ps-tryon-error svg { color: #ef4444; margin-bottom: 12px; }
1300
+ .ps-tryon-error-text { font-size: 14px; color: #ef4444; margin: 0 0 16px; }
1301
+
1302
+ /* Footer */
1303
+ .ps-tryon-powered { text-align: center; padding: 12px 24px 16px; font-size: 11px; color: #666; }
1304
+ .ps-tryon-powered a { color: #bb945c; text-decoration: none; }
1305
+ .ps-tryon-powered a:hover { text-decoration: underline; }
1306
+
1307
+ /* Drawer */
1308
+ .ps-tryon-drawer {
1309
+ position: absolute; inset: 0; z-index: 20; background: var(--ps-modal-bg, #111211);
1310
+ overflow-y: auto; scrollbar-width: thin; padding: 20px 24px;
1311
+ transform: translateX(100%); transition: transform 0.25s cubic-bezier(0.4,0,0.2,1);
1312
+ }
1313
+ .ps-tryon-drawer-open { transform: translateX(0); }
1314
+ .ps-tryon-drawer-header { display: flex; align-items: center; gap: 10px; padding-bottom: 14px; margin-bottom: 14px; border-bottom: 1px solid #333; }
1315
+ .ps-tryon-drawer-back {
1316
+ width: 32px; height: 32px; display: flex; align-items: center; justify-content: center;
1317
+ border: 1.5px solid #333; border-radius: 10px; background: transparent;
1318
+ cursor: pointer; color: #999; transition: all 0.2s; flex-shrink: 0;
1319
+ }
1320
+ .ps-tryon-drawer-back:hover { border-color: #bb945c; color: #bb945c; }
1321
+ .ps-tryon-drawer-title { font-size: 16px; font-weight: 600; color: #fff; }
1322
+ .ps-tryon-drawer-list { display: flex; flex-direction: column; gap: 8px; }
1323
+ .ps-tryon-drawer-empty { text-align: center; padding: 32px 16px; color: #666; font-size: 14px; }
1324
+
1325
+ /* Profile items */
1326
+ .ps-tryon-profile-item {
1327
+ display: flex; align-items: center; gap: 12px; padding: 14px;
1328
+ border: 1px solid #333; border-radius: 12px; cursor: pointer; transition: all 0.2s;
1329
+ }
1330
+ .ps-tryon-profile-item:hover { border-color: #bb945c; }
1331
+ .ps-tryon-profile-avatar {
1332
+ width: 40px; height: 40px; border-radius: 50%;
1333
+ display: flex; align-items: center; justify-content: center;
1334
+ background: rgba(187,148,92,0.1); flex-shrink: 0;
1335
+ }
1336
+ .ps-tryon-profile-avatar svg { stroke: #bb945c; fill: none; }
1337
+ .ps-tryon-profile-info { flex: 1; min-width: 0; }
1338
+ .ps-tryon-profile-name { font-size: 14px; font-weight: 600; color: #fff; }
1339
+ .ps-tryon-profile-detail { font-size: 11px; color: #999; margin-top: 2px; }
1340
+ .ps-tryon-profile-item > svg:last-child { color: #666; flex-shrink: 0; transition: color 0.2s; }
1341
+ .ps-tryon-profile-item:hover > svg:last-child { color: #bb945c; }
1342
+
1343
+ /* History items */
1344
+ .ps-tryon-history-item {
1345
+ display: flex; gap: 10px; padding: 12px;
1346
+ border: 1px solid #333; border-radius: 12px; align-items: center; transition: all 0.2s;
1347
+ }
1348
+ .ps-tryon-history-item:hover { border-color: #bb945c; }
1349
+ .ps-tryon-history-thumb { width: 48px; height: 60px; border-radius: 8px; object-fit: cover; flex-shrink: 0; }
1350
+ .ps-tryon-history-info { flex: 1; min-width: 0; }
1351
+ .ps-tryon-history-product { font-size: 13px; font-weight: 600; color: #fff; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
1352
+ .ps-tryon-history-meta { font-size: 11px; color: #666; margin-top: 3px; }
1353
+ .ps-tryon-history-sizing { display: flex; align-items: center; gap: 6px; margin-top: 6px; }
1354
+ .ps-tryon-history-size-badge {
1355
+ width: 28px; height: 28px; border-radius: 50%;
1356
+ display: flex; align-items: center; justify-content: center;
1357
+ background: linear-gradient(135deg, #bb945c, #d6ba7d);
1358
+ color: #111; font-size: 11px; font-weight: 700; flex-shrink: 0;
1359
+ }
1360
+ .ps-tryon-history-result-img { width: 48px; height: 60px; border-radius: 8px; object-fit: cover; flex-shrink: 0; border: 1.5px solid #bb945c; }
1361
+ .ps-tryon-history-delete {
1362
+ width: 28px; height: 28px; display: flex; align-items: center; justify-content: center;
1363
+ border: none; background: transparent; cursor: pointer; color: #666; border-radius: 6px; transition: all 0.2s; flex-shrink: 0;
1364
+ }
1365
+ .ps-tryon-history-delete:hover { background: rgba(239,68,68,0.1); color: #ef4444; }
1366
+ .ps-tryon-history-delete svg { stroke: currentColor; fill: none; }
1367
+
1368
+ /* Profile detail modal */
1369
+ .ps-tryon-detail-overlay {
1370
+ position: fixed; inset: 0; background: rgba(0,0,0,0.55);
1371
+ display: flex; align-items: center; justify-content: center;
1372
+ z-index: 9999999; padding: 16px; animation: ps-fade-in 0.2s ease;
1373
+ }
1374
+ .ps-tryon-detail-modal {
1375
+ background: #111211; border-radius: 16px; width: 100%; max-width: 440px; max-height: 85vh;
1376
+ overflow-y: auto; box-shadow: 0 32px 64px rgba(0,0,0,0.3); animation: ps-slide-up 0.25s ease;
1377
+ font-family: var(--ps-modal-font, system-ui, sans-serif); color: #fff;
1378
+ }
1379
+ .ps-tryon-detail-header {
1380
+ display: flex; align-items: center; justify-content: space-between;
1381
+ padding: 18px 24px; border-bottom: 1px solid #333;
1382
+ }
1383
+ .ps-tryon-detail-header span { font-size: 15px; font-weight: 600; }
1384
+ .ps-tryon-detail-header button { background: none; border: none; color: #999; cursor: pointer; display: flex; align-items: center; border-radius: 6px; padding: 4px; transition: background 0.15s; }
1385
+ .ps-tryon-detail-header button:hover { background: rgba(255,255,255,0.1); }
1386
+ .ps-tryon-detail-body { padding: 20px 24px; }
1387
+ .ps-tryon-detail-gender { display: flex; align-items: center; gap: 8px; font-size: 14px; font-weight: 600; padding-bottom: 14px; border-bottom: 1px solid #333; margin-bottom: 14px; }
1388
+ .ps-tryon-detail-gender svg { stroke: #bb945c; fill: none; }
1389
+ .ps-tryon-detail-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin-bottom: 16px; }
1390
+ .ps-tryon-detail-cell { background: #1a1b1a; border-radius: 10px; padding: 12px 14px; }
1391
+ .ps-tryon-detail-cell-label { font-size: 11px; color: #999; text-transform: uppercase; letter-spacing: 0.04em; margin-bottom: 4px; }
1392
+ .ps-tryon-detail-cell-value { font-size: 16px; font-weight: 600; color: #fff; }
1393
+ .ps-tryon-detail-date { font-size: 11px; color: #666; text-align: center; margin-top: 8px; }
1394
+ .ps-tryon-detail-delete {
1395
+ width: 100%; display: flex; align-items: center; justify-content: center; gap: 6px;
1396
+ padding: 12px; border: 1px solid #333; border-radius: 10px; background: none;
1397
+ color: #ef4444; font-size: 13px; font-weight: 500; cursor: pointer; transition: all 0.2s;
1398
+ font-family: inherit; margin-top: 16px;
1399
+ }
1400
+ .ps-tryon-detail-delete:hover { background: rgba(239,68,68,0.06); border-color: #ef4444; }
1401
+ .ps-tryon-detail-delete svg { stroke: currentColor; fill: none; }
1402
+
1403
+ /* Mobile responsive */
1404
+ @media (max-width: 720px) {
1405
+ .ps-tryon-result-split { flex-direction: column; }
1406
+ .ps-tryon-result-image-col { flex: none; }
1407
+ .ps-tryon-modal-wide { max-width: 520px; }
1408
+ }
1409
+ @media (max-width: 480px) {
1410
+ .ps-tryon-modal { max-height: 100vh; border-radius: 14px; }
1411
+ .ps-tryon-body { padding: 18px; }
1412
+ .ps-tryon-header { padding: 14px 18px; }
1413
+ .ps-tryon-stepper { padding: 14px 18px 8px; }
1414
+ .ps-tryon-stepper-circle { width: 24px; height: 24px; font-size: 10px; }
1415
+ .ps-tryon-stepper-label { font-size: 9px; }
1416
+ .ps-tryon-features { flex-direction: column; gap: 8px; }
1417
+ .ps-tryon-feature { flex-direction: row; gap: 10px; text-align: left; }
1418
+ .ps-tryon-feature-icon { margin-bottom: 0; }
1419
+ .ps-tryon-input-row { flex-wrap: wrap; }
1420
+ .ps-tryon-input-row label { min-width: 100%; margin-bottom: -4px; }
1421
+ .ps-tryon-drawer { padding: 16px; }
1422
+ .ps-tryon-detail-grid { gap: 8px; }
1423
+ .ps-tryon-detail-cell { padding: 10px 12px; }
1424
+ .ps-tryon-detail-cell-value { font-size: 14px; }
1425
+ .ps-tryon-detail-modal { max-width: 100%; }
1426
+ .ps-tryon-header-icon { width: 30px; height: 30px; border-radius: 8px; }
1427
+ }
1428
+ `;
894
1429
  export {
895
1430
  PrimeStyleTryon
896
1431
  };