@primestyleai/tryon 3.15.0 → 3.16.0
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.
|
@@ -306,6 +306,7 @@ const en = {
|
|
|
306
306
|
"tap to compare": "tap to compare",
|
|
307
307
|
"Comparing size": "Comparing size",
|
|
308
308
|
"Fit Analysis": "Fit Analysis",
|
|
309
|
+
"Show more": "Show more",
|
|
309
310
|
"Done": "Done",
|
|
310
311
|
// ── Try-on result ───────────────────────────────────
|
|
311
312
|
"Try-on result": "Try-on result",
|
package/dist/primestyle-tryon.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { c as createT, A as ApiClient, S as SseClient, i as isValidImageFile, a as compressImage } from "./index-
|
|
2
|
-
import { P, b, T, d, r } from "./index-
|
|
1
|
+
import { c as createT, A as ApiClient, S as SseClient, i as isValidImageFile, a as compressImage } from "./index-DvAzTRFF.js";
|
|
2
|
+
import { P, b, T, d, r } from "./index-DvAzTRFF.js";
|
|
3
3
|
function detectProductImage() {
|
|
4
4
|
const ogImage = document.querySelector(
|
|
5
5
|
'meta[property="og:image"]'
|
package/dist/react/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx, jsxs, Fragment } from "react/jsx-runtime";
|
|
3
3
|
import { useState, useEffect, useMemo, useRef, useCallback } from "react";
|
|
4
|
-
import { c as createT, A as ApiClient, S as SseClient, i as isValidImageFile, a as compressImage, P as PrimeStyleError, L as LOCALE_LABELS, b as SUPPORTED_LOCALES } from "../index-
|
|
4
|
+
import { c as createT, A as ApiClient, S as SseClient, i as isValidImageFile, a as compressImage, P as PrimeStyleError, L as LOCALE_LABELS, b as SUPPORTED_LOCALES } from "../index-DvAzTRFF.js";
|
|
5
5
|
const HEADER_ALIASES = {
|
|
6
6
|
// ── Size label columns (skipped during field derivation) ──
|
|
7
7
|
size: "__size__",
|
|
@@ -406,20 +406,6 @@ const FALLBACK_FIELDS_MALE = [
|
|
|
406
406
|
{ key: "inseam", label: "Inseam", required: false, unit: "cm", placeholder: "e.g. 81", category: "body" },
|
|
407
407
|
{ key: "footLengthCm", label: "Foot length", required: false, unit: "cm", placeholder: "e.g. 27", category: "shoe" }
|
|
408
408
|
];
|
|
409
|
-
const SIZING_COUNTRIES = [
|
|
410
|
-
{ code: "US", label: "United States" },
|
|
411
|
-
{ code: "UK", label: "United Kingdom" },
|
|
412
|
-
{ code: "EU", label: "Europe (EU)" },
|
|
413
|
-
{ code: "FR", label: "France" },
|
|
414
|
-
{ code: "IT", label: "Italy" },
|
|
415
|
-
{ code: "DE", label: "Germany" },
|
|
416
|
-
{ code: "ES", label: "Spain" },
|
|
417
|
-
{ code: "JP", label: "Japan" },
|
|
418
|
-
{ code: "CN", label: "China" },
|
|
419
|
-
{ code: "KR", label: "South Korea" },
|
|
420
|
-
{ code: "AU", label: "Australia" },
|
|
421
|
-
{ code: "BR", label: "Brazil" }
|
|
422
|
-
];
|
|
423
409
|
const STEP_LABELS = ["", "Welcome", "Size", "Your Fit", "Try On"];
|
|
424
410
|
const TOTAL_STEPS = 4;
|
|
425
411
|
function detectLocale() {
|
|
@@ -840,7 +826,107 @@ function PrimeStyleTryonInner({
|
|
|
840
826
|
}
|
|
841
827
|
return formGender === "female" ? FALLBACK_FIELDS_FEMALE : FALLBACK_FIELDS_MALE;
|
|
842
828
|
}, [sizeGuide, formGender]);
|
|
829
|
+
const computeSizingLocally = useCallback(() => {
|
|
830
|
+
if (sizingMethod !== "exact" || !sizeGuide?.found || !sizeGuide.headers || !sizeGuide.rows) return null;
|
|
831
|
+
const HEADER_MAP = {
|
|
832
|
+
chest: "chest",
|
|
833
|
+
bust: "bust",
|
|
834
|
+
waist: "waist",
|
|
835
|
+
hips: "hips",
|
|
836
|
+
hip: "hips",
|
|
837
|
+
shoulder: "shoulderWidth",
|
|
838
|
+
shoulders: "shoulderWidth",
|
|
839
|
+
"shoulder width": "shoulderWidth",
|
|
840
|
+
sleeve: "sleeveLength",
|
|
841
|
+
"sleeve length": "sleeveLength",
|
|
842
|
+
inseam: "inseam",
|
|
843
|
+
neck: "neckCircumference",
|
|
844
|
+
foot: "footLengthCm",
|
|
845
|
+
"foot length": "footLengthCm"
|
|
846
|
+
};
|
|
847
|
+
const INTL = /* @__PURE__ */ new Set(["eu", "us", "uk", "it", "fr", "de", "jp", "cn", "kr", "au", "br", "eur"]);
|
|
848
|
+
const userMeas = {};
|
|
849
|
+
for (const f of dynamicFields) {
|
|
850
|
+
if (f.unit === "size" || ["shoeEU", "shoeUS", "shoeUK"].includes(f.key)) continue;
|
|
851
|
+
const raw = formRef.current[f.key];
|
|
852
|
+
if (!raw) continue;
|
|
853
|
+
const v = parseFloat(raw);
|
|
854
|
+
if (isNaN(v)) continue;
|
|
855
|
+
userMeas[f.key] = sizingUnit === "in" ? inToCm(v) : v;
|
|
856
|
+
}
|
|
857
|
+
for (const k of ["chest", "bust", "waist", "hips", "shoulderWidth", "sleeveLength", "inseam", "neckCircumference", "footLengthCm"]) {
|
|
858
|
+
if (userMeas[k] || !formRef.current[k]) continue;
|
|
859
|
+
const v = parseFloat(formRef.current[k]);
|
|
860
|
+
if (!isNaN(v)) userMeas[k] = sizingUnit === "in" ? inToCm(v) : v;
|
|
861
|
+
}
|
|
862
|
+
if (Object.keys(userMeas).length === 0) return null;
|
|
863
|
+
const sizeColIdx = sizeGuide.headers.findIndex((h) => /^size$/i.test(h.trim()));
|
|
864
|
+
const idx = sizeColIdx >= 0 ? sizeColIdx : 0;
|
|
865
|
+
const colMap = [];
|
|
866
|
+
const intlCols = [];
|
|
867
|
+
sizeGuide.headers.forEach((h, i) => {
|
|
868
|
+
if (i === idx) return;
|
|
869
|
+
const lower = h.toLowerCase().trim().replace(/\s*\(.*\)/, "");
|
|
870
|
+
const clean = lower.replace(/\s*size\s*/gi, "").trim();
|
|
871
|
+
if (INTL.has(clean)) {
|
|
872
|
+
intlCols.push({ hi: i, code: clean.toUpperCase() });
|
|
873
|
+
return;
|
|
874
|
+
}
|
|
875
|
+
const fk = HEADER_MAP[clean];
|
|
876
|
+
if (fk && userMeas[fk] !== void 0) colMap.push({ hi: i, formKey: fk, label: h.trim() });
|
|
877
|
+
});
|
|
878
|
+
if (colMap.length === 0) return null;
|
|
879
|
+
const parseRange = (cell) => {
|
|
880
|
+
const nums = cell.replace(/[^\d.\-–]/g, " ").trim().split(/[\s\-–]+/).filter(Boolean).map(Number).filter((n) => !isNaN(n));
|
|
881
|
+
if (nums.length === 0) return null;
|
|
882
|
+
return { min: Math.min(...nums), max: Math.max(...nums) };
|
|
883
|
+
};
|
|
884
|
+
const scores = [];
|
|
885
|
+
for (const row of sizeGuide.rows) {
|
|
886
|
+
const label = row[idx] || "";
|
|
887
|
+
let fitting = 0;
|
|
888
|
+
let total = 0;
|
|
889
|
+
let dist = 0;
|
|
890
|
+
const details = [];
|
|
891
|
+
for (const col of colMap) {
|
|
892
|
+
const range = parseRange(row[col.hi] || "");
|
|
893
|
+
if (!range) continue;
|
|
894
|
+
total++;
|
|
895
|
+
const uv = userMeas[col.formKey];
|
|
896
|
+
const fit = uv >= range.min && uv <= range.max ? "good" : uv < range.min ? "tight" : "loose";
|
|
897
|
+
if (fit === "good") fitting++;
|
|
898
|
+
dist += Math.abs(uv - (range.min + range.max) / 2);
|
|
899
|
+
const dispVal = sizingUnit === "in" ? `${cmToIn(uv)} in` : `${Math.round(uv)} cm`;
|
|
900
|
+
const dispRange = sizingUnit === "in" ? `${cmToIn(range.min)}–${cmToIn(range.max)} in` : `${Math.round(range.min)}–${Math.round(range.max)} cm`;
|
|
901
|
+
details.push({ measurement: col.label, userValue: dispVal, chartRange: dispRange, fit });
|
|
902
|
+
}
|
|
903
|
+
if (total === 0) continue;
|
|
904
|
+
const intl = {};
|
|
905
|
+
for (const ic of intlCols) {
|
|
906
|
+
if (row[ic.hi]) intl[ic.code] = row[ic.hi];
|
|
907
|
+
}
|
|
908
|
+
scores.push({ label, fitting, total, dist, details, intl, row });
|
|
909
|
+
}
|
|
910
|
+
if (scores.length === 0) return null;
|
|
911
|
+
scores.sort((a, b) => b.fitting - a.fitting || a.dist - b.dist);
|
|
912
|
+
const best = scores[0];
|
|
913
|
+
const conf = best.fitting === best.total ? "high" : best.fitting >= best.total * 0.6 ? "medium" : "low";
|
|
914
|
+
return {
|
|
915
|
+
recommendedSize: best.label,
|
|
916
|
+
confidence: conf,
|
|
917
|
+
reasoning: `Based on your measurements, size ${best.label} is the best fit.`,
|
|
918
|
+
internationalSizes: best.intl,
|
|
919
|
+
matchDetails: best.details,
|
|
920
|
+
method: "deterministic"
|
|
921
|
+
};
|
|
922
|
+
}, [sizingMethod, sizeGuide, dynamicFields, sizingUnit]);
|
|
843
923
|
const submitSizing = useCallback(async () => {
|
|
924
|
+
const localResult = computeSizingLocally();
|
|
925
|
+
if (localResult) {
|
|
926
|
+
setSizingResult(localResult);
|
|
927
|
+
setSizingLoading(false);
|
|
928
|
+
return;
|
|
929
|
+
}
|
|
844
930
|
if (!apiRef.current) return;
|
|
845
931
|
const baseUrl = getApiUrl(apiUrl);
|
|
846
932
|
const key = getApiKey();
|
|
@@ -899,7 +985,7 @@ function PrimeStyleTryonInner({
|
|
|
899
985
|
} finally {
|
|
900
986
|
setSizingLoading(false);
|
|
901
987
|
}
|
|
902
|
-
}, [apiUrl, sizingMethod, sizingCountry, heightUnit, weightUnit, sizingUnit, sizeGuide, productTitle, dynamicFields]);
|
|
988
|
+
}, [apiUrl, sizingMethod, sizingCountry, heightUnit, weightUnit, sizingUnit, sizeGuide, productTitle, dynamicFields, computeSizingLocally]);
|
|
903
989
|
const handleTryOnSubmit = useCallback(async () => {
|
|
904
990
|
if (!selectedFile || !apiRef.current || !sseRef.current) {
|
|
905
991
|
const msg = !apiRef.current ? t("SDK not configured. Please provide an API key.") : t("Something went wrong");
|
|
@@ -1339,10 +1425,6 @@ function PrimeStyleTryonInner({
|
|
|
1339
1425
|
")"
|
|
1340
1426
|
] }, p.id))
|
|
1341
1427
|
] }) }),
|
|
1342
|
-
/* @__PURE__ */ jsxs("div", { className: "ps-tryon-input-row", children: [
|
|
1343
|
-
/* @__PURE__ */ jsx("label", { children: t("Sizing region") }),
|
|
1344
|
-
/* @__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: t(c.label) }, c.code)) })
|
|
1345
|
-
] }),
|
|
1346
1428
|
sizingMethod === "exact" && /* @__PURE__ */ jsxs("div", { className: "ps-tryon-unit-tabs", children: [
|
|
1347
1429
|
/* @__PURE__ */ jsx("button", { className: `ps-tryon-unit-tab${isCm ? " ps-active" : ""}`, onClick: () => {
|
|
1348
1430
|
setSizingUnit("cm");
|
|
@@ -1599,13 +1681,30 @@ function PrimeStyleTryonInner({
|
|
|
1599
1681
|
/* @__PURE__ */ jsx("div", { className: `ps-tryon-sr-hero-conf ps-conf-${sizingResult.confidence}`, children: sizingResult.confidence === "high" ? t("High Confidence") : sizingResult.confidence === "medium" ? t("Medium Confidence") : t("Low Confidence") })
|
|
1600
1682
|
] })
|
|
1601
1683
|
] }),
|
|
1602
|
-
Object.keys(activeIntl).length > 0 &&
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1684
|
+
Object.keys(activeIntl).length > 0 && (() => {
|
|
1685
|
+
const PRIMARY_CODES = ["US", "UK", "EU", "IT", "FR", "DE"];
|
|
1686
|
+
const primary = PRIMARY_CODES.filter((c) => activeIntl[c]).map((c) => ({ code: c, val: activeIntl[c] }));
|
|
1687
|
+
const rest = Object.entries(activeIntl).filter(([c]) => !PRIMARY_CODES.includes(c)).map(([code, val]) => ({ code, val }));
|
|
1688
|
+
return /* @__PURE__ */ jsxs("div", { className: "ps-tryon-sr-intl", children: [
|
|
1689
|
+
/* @__PURE__ */ jsx("div", { className: "ps-tryon-sr-label", children: t("Equivalent Sizes") }),
|
|
1690
|
+
primary.length > 0 && /* @__PURE__ */ jsx("div", { className: "ps-tryon-sr-intl-primary", children: primary.map((s) => /* @__PURE__ */ jsxs("div", { className: "ps-tryon-sr-intl-card", children: [
|
|
1691
|
+
/* @__PURE__ */ jsx("span", { className: "ps-tryon-sr-intl-card-val", children: s.val }),
|
|
1692
|
+
/* @__PURE__ */ jsx("span", { className: "ps-tryon-sr-intl-card-code", children: s.code })
|
|
1693
|
+
] }, s.code)) }),
|
|
1694
|
+
rest.length > 0 && /* @__PURE__ */ jsxs("details", { className: "ps-tryon-sr-intl-more", children: [
|
|
1695
|
+
/* @__PURE__ */ jsxs("summary", { className: "ps-tryon-sr-intl-more-btn", children: [
|
|
1696
|
+
t("Show more"),
|
|
1697
|
+
" (",
|
|
1698
|
+
rest.length,
|
|
1699
|
+
")"
|
|
1700
|
+
] }),
|
|
1701
|
+
/* @__PURE__ */ jsx("div", { className: "ps-tryon-sr-intl-grid", children: rest.map((s) => /* @__PURE__ */ jsxs("div", { className: "ps-tryon-sr-intl-item", children: [
|
|
1702
|
+
/* @__PURE__ */ jsx("span", { className: "ps-tryon-sr-intl-code", children: s.code }),
|
|
1703
|
+
/* @__PURE__ */ jsx("span", { className: "ps-tryon-sr-intl-val", children: s.val })
|
|
1704
|
+
] }, s.code)) })
|
|
1705
|
+
] })
|
|
1706
|
+
] });
|
|
1707
|
+
})(),
|
|
1609
1708
|
chartData && chartData.sizes.length > 0 && /* @__PURE__ */ jsxs("div", { className: "ps-tryon-sr-sizes", children: [
|
|
1610
1709
|
/* @__PURE__ */ jsxs("div", { className: "ps-tryon-sr-label", children: [
|
|
1611
1710
|
t("Size Chart"),
|
|
@@ -2376,6 +2475,25 @@ const STYLES = `
|
|
|
2376
2475
|
.ps-tryon-sr-intl { }
|
|
2377
2476
|
.ps-tryon-sr-label { font-size: 0.78vw; font-weight: 700; color: #999; text-transform: uppercase; letter-spacing: 0.08em; margin-bottom: 0.52vw; }
|
|
2378
2477
|
.ps-tryon-sr-label-hint { font-weight: 400; text-transform: none; letter-spacing: 0; color: #666; font-style: italic; }
|
|
2478
|
+
.ps-tryon-sr-intl-primary { display: flex; flex-wrap: wrap; gap: 0.42vw; margin-bottom: 0.52vw; }
|
|
2479
|
+
.ps-tryon-sr-intl-card {
|
|
2480
|
+
flex: 1; min-width: 3.5vw; display: flex; flex-direction: column; align-items: center;
|
|
2481
|
+
padding: 0.57vw 0.42vw; border: 1.5px solid #333; border-radius: 0.63vw;
|
|
2482
|
+
background: #1a1b1a; transition: border-color 0.2s;
|
|
2483
|
+
}
|
|
2484
|
+
.ps-tryon-sr-intl-card:hover { border-color: #555; }
|
|
2485
|
+
.ps-tryon-sr-intl-card-val { font-size: 1.04vw; font-weight: 800; color: #fff; line-height: 1.2; }
|
|
2486
|
+
.ps-tryon-sr-intl-card-code { font-size: 0.57vw; font-weight: 700; color: #666; text-transform: uppercase; letter-spacing: 0.08em; margin-top: 0.1vw; }
|
|
2487
|
+
|
|
2488
|
+
.ps-tryon-sr-intl-more { }
|
|
2489
|
+
.ps-tryon-sr-intl-more-btn {
|
|
2490
|
+
font-size: 0.73vw; color: #bb945c; font-weight: 600; cursor: pointer;
|
|
2491
|
+
list-style: none; margin-bottom: 0.42vw; font-family: inherit;
|
|
2492
|
+
}
|
|
2493
|
+
.ps-tryon-sr-intl-more-btn::-webkit-details-marker { display: none; }
|
|
2494
|
+
.ps-tryon-sr-intl-more-btn::before { content: "▸ "; }
|
|
2495
|
+
.ps-tryon-sr-intl-more[open] .ps-tryon-sr-intl-more-btn::before { content: "▾ "; }
|
|
2496
|
+
|
|
2379
2497
|
.ps-tryon-sr-intl-grid { display: flex; flex-wrap: wrap; gap: 0.42vw; }
|
|
2380
2498
|
.ps-tryon-sr-intl-item {
|
|
2381
2499
|
display: flex; align-items: center; border: 1.5px solid #333; border-radius: 0.52vw; overflow: hidden;
|