@primestyleai/tryon 3.14.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.
@@ -302,6 +302,11 @@ const en = {
302
302
  "Equivalent Sizes": "Equivalent Sizes",
303
303
  "Analyzing your size...": "Analyzing your size...",
304
304
  "Your size:": "Your size:",
305
+ "Size Chart": "Size Chart",
306
+ "tap to compare": "tap to compare",
307
+ "Comparing size": "Comparing size",
308
+ "Fit Analysis": "Fit Analysis",
309
+ "Show more": "Show more",
305
310
  "Done": "Done",
306
311
  // ── Try-on result ───────────────────────────────────
307
312
  "Try-on result": "Try-on result",
@@ -1,5 +1,5 @@
1
- import { c as createT, A as ApiClient, S as SseClient, i as isValidImageFile, a as compressImage } from "./index-25Bm_pob.js";
2
- import { P, b, T, d, r } from "./index-25Bm_pob.js";
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"]'
@@ -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-25Bm_pob.js";
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() {
@@ -438,6 +424,9 @@ function detectLocale() {
438
424
  function isImperial(locale) {
439
425
  return locale === "US" || locale === "UK";
440
426
  }
427
+ function cmToIn(cm) {
428
+ return +(cm / 2.54).toFixed(1);
429
+ }
441
430
  function inToCm(inches) {
442
431
  return +(inches * 2.54).toFixed(1);
443
432
  }
@@ -837,7 +826,107 @@ function PrimeStyleTryonInner({
837
826
  }
838
827
  return formGender === "female" ? FALLBACK_FIELDS_FEMALE : FALLBACK_FIELDS_MALE;
839
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]);
840
923
  const submitSizing = useCallback(async () => {
924
+ const localResult = computeSizingLocally();
925
+ if (localResult) {
926
+ setSizingResult(localResult);
927
+ setSizingLoading(false);
928
+ return;
929
+ }
841
930
  if (!apiRef.current) return;
842
931
  const baseUrl = getApiUrl(apiUrl);
843
932
  const key = getApiKey();
@@ -896,7 +985,7 @@ function PrimeStyleTryonInner({
896
985
  } finally {
897
986
  setSizingLoading(false);
898
987
  }
899
- }, [apiUrl, sizingMethod, sizingCountry, heightUnit, weightUnit, sizingUnit, sizeGuide, productTitle, dynamicFields]);
988
+ }, [apiUrl, sizingMethod, sizingCountry, heightUnit, weightUnit, sizingUnit, sizeGuide, productTitle, dynamicFields, computeSizingLocally]);
900
989
  const handleTryOnSubmit = useCallback(async () => {
901
990
  if (!selectedFile || !apiRef.current || !sseRef.current) {
902
991
  const msg = !apiRef.current ? t("SDK not configured. Please provide an API key.") : t("Something went wrong");
@@ -1336,10 +1425,6 @@ function PrimeStyleTryonInner({
1336
1425
  ")"
1337
1426
  ] }, p.id))
1338
1427
  ] }) }),
1339
- /* @__PURE__ */ jsxs("div", { className: "ps-tryon-input-row", children: [
1340
- /* @__PURE__ */ jsx("label", { children: t("Sizing region") }),
1341
- /* @__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)) })
1342
- ] }),
1343
1428
  sizingMethod === "exact" && /* @__PURE__ */ jsxs("div", { className: "ps-tryon-unit-tabs", children: [
1344
1429
  /* @__PURE__ */ jsx("button", { className: `ps-tryon-unit-tab${isCm ? " ps-active" : ""}`, onClick: () => {
1345
1430
  setSizingUnit("cm");
@@ -1489,63 +1574,208 @@ function PrimeStyleTryonInner({
1489
1574
  ] }, `form-${formGender}-${sizingUnit}-${heightUnit}-${sizingCountry}-${formKey}`);
1490
1575
  }
1491
1576
  function SizeResultView() {
1492
- const [showFitDetails, setShowFitDetails] = useState(false);
1493
- const confidenceLabel = sizingResult?.confidence === "high" ? t("High Confidence") : sizingResult?.confidence === "medium" ? t("Medium Confidence") : sizingResult?.confidence === "low" ? t("Low Confidence") : "";
1494
- return /* @__PURE__ */ jsxs("div", { className: "ps-tryon-size-result-view", children: [
1495
- sizingLoading && !sizingResult && /* @__PURE__ */ jsxs("div", { className: "ps-tryon-size-recommend ps-tryon-sizing-loading", children: [
1577
+ const [activeSize, setActiveSize] = useState(sizingResult?.recommendedSize || "");
1578
+ const [editedValues, setEditedValues] = useState({});
1579
+ const isCmResult = sizingUnit === "cm";
1580
+ const chartData = useMemo(() => {
1581
+ if (!sizeGuide?.found || !sizeGuide.headers || !sizeGuide.rows) return null;
1582
+ const INTL = /* @__PURE__ */ new Set(["eu", "us", "uk", "it", "fr", "de", "jp", "cn", "kr", "au", "br", "eur"]);
1583
+ const MEAS_MAP = {
1584
+ chest: "chest",
1585
+ bust: "bust",
1586
+ waist: "waist",
1587
+ hips: "hips",
1588
+ hip: "hips",
1589
+ shoulder: "shoulderWidth",
1590
+ shoulders: "shoulderWidth",
1591
+ "shoulder width": "shoulderWidth",
1592
+ sleeve: "sleeveLength",
1593
+ "sleeve length": "sleeveLength",
1594
+ inseam: "inseam",
1595
+ neck: "neckCircumference",
1596
+ "neck circumference": "neckCircumference",
1597
+ foot: "footLengthCm",
1598
+ "foot length": "footLengthCm"
1599
+ };
1600
+ const sizeColIdx = sizeGuide.headers.findIndex((h) => /^size$/i.test(h.trim()));
1601
+ const idx = sizeColIdx >= 0 ? sizeColIdx : 0;
1602
+ const measCols = [];
1603
+ const intlCols = [];
1604
+ sizeGuide.headers.forEach((h, i) => {
1605
+ if (i === idx) return;
1606
+ const lower = h.toLowerCase().trim().replace(/\s*\(.*\)/, "");
1607
+ const clean = lower.replace(/\s*size\s*/gi, "").trim();
1608
+ if (INTL.has(clean)) {
1609
+ intlCols.push({ headerIdx: i, code: clean.toUpperCase() });
1610
+ } else if (MEAS_MAP[clean]) {
1611
+ measCols.push({ headerIdx: i, label: h.trim(), formKey: MEAS_MAP[clean] });
1612
+ } else if (lower !== "size") {
1613
+ measCols.push({ headerIdx: i, label: h.trim(), formKey: lower.replace(/\s+/g, "") });
1614
+ }
1615
+ });
1616
+ const sizes = sizeGuide.rows.map((row) => {
1617
+ const label = row[idx] || "";
1618
+ const measurements = {};
1619
+ for (const col of measCols) {
1620
+ const cell = row[col.headerIdx] || "";
1621
+ const nums = cell.replace(/[^\d.\-–]/g, " ").trim().split(/[\s\-–]+/).filter(Boolean).map(Number).filter((n) => !isNaN(n));
1622
+ if (nums.length > 0) {
1623
+ measurements[col.formKey] = { raw: cell, min: Math.min(...nums), max: Math.max(...nums) };
1624
+ }
1625
+ }
1626
+ const intl = {};
1627
+ for (const col of intlCols) {
1628
+ if (row[col.headerIdx]) intl[col.code] = row[col.headerIdx];
1629
+ }
1630
+ return { label, measurements, intl };
1631
+ });
1632
+ return { sizes, measCols, intlCols };
1633
+ }, [sizeGuide]);
1634
+ const getUserVal = useCallback((formKey2) => {
1635
+ const edited = editedValues[formKey2];
1636
+ if (edited !== void 0 && edited !== "") {
1637
+ const v2 = parseFloat(edited);
1638
+ return isNaN(v2) ? null : isCmResult ? v2 : inToCm(v2);
1639
+ }
1640
+ const raw = formRef.current[formKey2];
1641
+ if (!raw) return null;
1642
+ const v = parseFloat(raw);
1643
+ return isNaN(v) ? null : isCmResult ? v : inToCm(v);
1644
+ }, [editedValues, isCmResult]);
1645
+ const activeFit = useMemo(() => {
1646
+ if (!chartData) return null;
1647
+ const sizeRow = chartData.sizes.find((s) => s.label === activeSize);
1648
+ if (!sizeRow) return null;
1649
+ const details = [];
1650
+ for (const col of chartData.measCols) {
1651
+ const range = sizeRow.measurements[col.formKey];
1652
+ if (!range) continue;
1653
+ const userVal = getUserVal(col.formKey);
1654
+ if (userVal === null) continue;
1655
+ const fit = userVal >= range.min && userVal <= range.max ? "good" : userVal < range.min ? "tight" : "loose";
1656
+ details.push({ label: col.label, formKey: col.formKey, userVal, chartMin: range.min, chartMax: range.max, chartRaw: range.raw, fit });
1657
+ }
1658
+ return details;
1659
+ }, [chartData, activeSize, getUserVal]);
1660
+ const activeIntl = useMemo(() => {
1661
+ if (!chartData) return sizingResult?.internationalSizes || {};
1662
+ const sizeRow = chartData.sizes.find((s) => s.label === activeSize);
1663
+ return sizeRow?.intl || sizingResult?.internationalSizes || {};
1664
+ }, [chartData, activeSize, sizingResult]);
1665
+ const fmtRange = (min, max) => {
1666
+ if (isCmResult) return min === max ? `${Math.round(min)}` : `${Math.round(min)}–${Math.round(max)}`;
1667
+ return min === max ? `${cmToIn(min)}` : `${cmToIn(min)}–${cmToIn(max)}`;
1668
+ };
1669
+ const unitLabel = isCmResult ? t("cm") : t("in");
1670
+ const isRecommended = activeSize === sizingResult?.recommendedSize;
1671
+ return /* @__PURE__ */ jsxs("div", { className: "ps-tryon-sr", children: [
1672
+ sizingLoading && !sizingResult && /* @__PURE__ */ jsxs("div", { className: "ps-tryon-sr-loading", children: [
1496
1673
  /* @__PURE__ */ jsx("div", { className: "ps-tryon-size-loading-spinner" }),
1497
- /* @__PURE__ */ jsx("p", { className: "ps-tryon-size-reasoning", children: t("Analyzing your size...") })
1674
+ /* @__PURE__ */ jsx("p", { children: t("Analyzing your size...") })
1498
1675
  ] }),
1499
1676
  sizingResult && /* @__PURE__ */ jsxs(Fragment, { children: [
1500
- /* @__PURE__ */ jsxs("div", { className: "ps-tryon-size-recommend", children: [
1501
- /* @__PURE__ */ jsx("h3", { className: "ps-tryon-size-title", children: t("Your Size") }),
1502
- /* @__PURE__ */ jsxs("div", { className: "ps-tryon-size-hero-row", children: [
1503
- /* @__PURE__ */ jsx("div", { className: "ps-tryon-size-badge", children: sizingResult.recommendedSize }),
1504
- /* @__PURE__ */ jsx("span", { className: `ps-tryon-size-conf-label ps-conf-${sizingResult.confidence}`, children: confidenceLabel })
1505
- ] }),
1506
- sizingResult.sections && Object.keys(sizingResult.sections).length > 1 && /* @__PURE__ */ jsxs("div", { className: "ps-tryon-multi-section", children: [
1507
- /* @__PURE__ */ jsx("h4", { className: "ps-tryon-fit-summary-title", children: t("Sizing by Garment") }),
1508
- Object.entries(sizingResult.sections).map(([name, sec]) => /* @__PURE__ */ jsxs("div", { className: "ps-tryon-section-row", children: [
1509
- /* @__PURE__ */ jsx("span", { className: "ps-tryon-section-name", children: name }),
1510
- /* @__PURE__ */ jsx("span", { className: "ps-tryon-size-badge ps-tryon-section-badge", children: sec.recommendedSize })
1511
- ] }, name))
1512
- ] }),
1513
- sizingResult.matchDetails && sizingResult.matchDetails.length > 0 && /* @__PURE__ */ jsxs("div", { className: "ps-tryon-fit-summary", children: [
1514
- /* @__PURE__ */ jsx("h4", { className: "ps-tryon-fit-summary-title", children: t("Fit Summary") }),
1515
- sizingResult.matchDetails.map((m, i) => /* @__PURE__ */ jsxs("div", { className: "ps-tryon-fit-row", children: [
1516
- /* @__PURE__ */ jsx("span", { className: `ps-tryon-fit-icon ps-fit-icon-${m.fit}`, children: m.fit === "good" ? "✓" : m.fit === "tight" ? "↑" : "↓" }),
1517
- /* @__PURE__ */ jsxs("span", { className: "ps-tryon-fit-text", children: [
1518
- /* @__PURE__ */ jsx("strong", { children: m.measurement }),
1519
- " ",
1520
- m.fit === "good" ? t("within range") : m.fit === "tight" ? t("may be snug") : t("may be loose")
1521
- ] })
1522
- ] }, i)),
1523
- /* @__PURE__ */ jsx("button", { className: "ps-tryon-fit-details-toggle", onClick: () => setShowFitDetails(!showFitDetails), children: showFitDetails ? `${t("Hide details")} ↑` : `${t("See details")} ↓` }),
1524
- showFitDetails && /* @__PURE__ */ jsxs("table", { className: "ps-tryon-fit-table", children: [
1525
- /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { children: [
1526
- /* @__PURE__ */ jsx("th", { children: t("Area") }),
1527
- /* @__PURE__ */ jsx("th", { children: t("You") }),
1528
- /* @__PURE__ */ jsx("th", { children: t("Chart") }),
1529
- /* @__PURE__ */ jsx("th", { children: t("Fit") })
1530
- ] }) }),
1531
- /* @__PURE__ */ jsx("tbody", { children: sizingResult.matchDetails.map((m, i) => /* @__PURE__ */ jsxs("tr", { children: [
1532
- /* @__PURE__ */ jsx("td", { children: m.measurement }),
1533
- /* @__PURE__ */ jsx("td", { children: m.userValue }),
1534
- /* @__PURE__ */ jsx("td", { children: m.chartRange }),
1535
- /* @__PURE__ */ jsx("td", { className: `ps-fit-${m.fit}`, children: m.fit })
1536
- ] }, i)) })
1677
+ /* @__PURE__ */ jsxs("div", { className: "ps-tryon-sr-hero", children: [
1678
+ /* @__PURE__ */ jsx("div", { className: "ps-tryon-sr-hero-badge", children: sizingResult.recommendedSize }),
1679
+ /* @__PURE__ */ jsxs("div", { className: "ps-tryon-sr-hero-info", children: [
1680
+ /* @__PURE__ */ jsx("div", { className: "ps-tryon-sr-hero-title", children: t("Your Size") }),
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") })
1682
+ ] })
1683
+ ] }),
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
+ })(),
1708
+ chartData && chartData.sizes.length > 0 && /* @__PURE__ */ jsxs("div", { className: "ps-tryon-sr-sizes", children: [
1709
+ /* @__PURE__ */ jsxs("div", { className: "ps-tryon-sr-label", children: [
1710
+ t("Size Chart"),
1711
+ " ",
1712
+ /* @__PURE__ */ jsxs("span", { className: "ps-tryon-sr-label-hint", children: [
1713
+ "— ",
1714
+ t("tap to compare")
1537
1715
  ] })
1538
1716
  ] }),
1539
- sizingResult.internationalSizes && Object.keys(sizingResult.internationalSizes).length > 0 && /* @__PURE__ */ jsxs("div", { className: "ps-tryon-equiv-section", children: [
1540
- /* @__PURE__ */ jsx("h4", { className: "ps-tryon-equiv-title", children: t("Equivalent Sizes") }),
1541
- /* @__PURE__ */ jsx("div", { className: "ps-tryon-equiv-chips", children: Object.entries(sizingResult.internationalSizes).map(([k, v]) => /* @__PURE__ */ jsxs("div", { className: "ps-tryon-equiv-chip", children: [
1542
- /* @__PURE__ */ jsx("span", { className: "ps-tryon-equiv-region", children: k }),
1543
- /* @__PURE__ */ jsx("span", { className: "ps-tryon-equiv-value", children: v })
1544
- ] }, k)) })
1545
- ] }),
1546
- (!sizingResult.matchDetails || sizingResult.matchDetails.length === 0) && sizingResult.reasoning && /* @__PURE__ */ jsx("p", { className: "ps-tryon-size-reasoning", children: sizingResult.reasoning })
1717
+ /* @__PURE__ */ jsx("div", { className: "ps-tryon-sr-size-row", children: chartData.sizes.map((s) => /* @__PURE__ */ jsxs(
1718
+ "button",
1719
+ {
1720
+ onClick: () => setActiveSize(s.label),
1721
+ className: `ps-tryon-sr-size-chip${s.label === activeSize ? " ps-active" : ""}${s.label === sizingResult.recommendedSize ? " ps-recommended" : ""}`,
1722
+ children: [
1723
+ s.label,
1724
+ s.label === sizingResult.recommendedSize && /* @__PURE__ */ jsx("span", { className: "ps-tryon-sr-rec-dot" })
1725
+ ]
1726
+ },
1727
+ s.label
1728
+ )) }),
1729
+ !isRecommended && /* @__PURE__ */ jsxs("div", { className: "ps-tryon-sr-comparing", children: [
1730
+ t("Comparing size"),
1731
+ " ",
1732
+ /* @__PURE__ */ jsx("strong", { children: activeSize })
1733
+ ] })
1547
1734
  ] }),
1548
- /* @__PURE__ */ jsxs("div", { className: "ps-tryon-tryon-cta", children: [
1735
+ activeFit && activeFit.length > 0 && /* @__PURE__ */ jsxs("div", { className: "ps-tryon-sr-fit", children: [
1736
+ /* @__PURE__ */ jsx("div", { className: "ps-tryon-sr-label", children: t("Fit Analysis") }),
1737
+ /* @__PURE__ */ jsxs("div", { className: "ps-tryon-sr-fit-table", children: [
1738
+ /* @__PURE__ */ jsxs("div", { className: "ps-tryon-sr-fit-header", children: [
1739
+ /* @__PURE__ */ jsx("span", { className: "ps-tryon-sr-fit-col-area", children: t("Area") }),
1740
+ /* @__PURE__ */ jsxs("span", { className: "ps-tryon-sr-fit-col-you", children: [
1741
+ t("You"),
1742
+ " (",
1743
+ unitLabel,
1744
+ ")"
1745
+ ] }),
1746
+ /* @__PURE__ */ jsxs("span", { className: "ps-tryon-sr-fit-col-chart", children: [
1747
+ t("Chart"),
1748
+ " (",
1749
+ unitLabel,
1750
+ ")"
1751
+ ] }),
1752
+ /* @__PURE__ */ jsx("span", { className: "ps-tryon-sr-fit-col-fit", children: t("Fit") })
1753
+ ] }),
1754
+ activeFit.map((row, i) => /* @__PURE__ */ jsxs("div", { className: `ps-tryon-sr-fit-row ps-fit-${row.fit}`, children: [
1755
+ /* @__PURE__ */ jsx("span", { className: "ps-tryon-sr-fit-col-area", children: row.label }),
1756
+ /* @__PURE__ */ jsx("span", { className: "ps-tryon-sr-fit-col-you", children: /* @__PURE__ */ jsx(
1757
+ "input",
1758
+ {
1759
+ type: "number",
1760
+ className: "ps-tryon-sr-fit-input",
1761
+ defaultValue: isCmResult ? Math.round(row.userVal) : cmToIn(row.userVal),
1762
+ onBlur: (e) => {
1763
+ const val = e.target.value;
1764
+ setEditedValues((prev) => ({ ...prev, [row.formKey]: val }));
1765
+ }
1766
+ }
1767
+ ) }),
1768
+ /* @__PURE__ */ jsx("span", { className: "ps-tryon-sr-fit-col-chart", children: fmtRange(row.chartMin, row.chartMax) }),
1769
+ /* @__PURE__ */ jsx("span", { className: "ps-tryon-sr-fit-col-fit", children: /* @__PURE__ */ jsxs("span", { className: `ps-tryon-sr-fit-badge ps-fit-${row.fit}`, children: [
1770
+ row.fit === "good" ? "✓" : row.fit === "tight" ? "↑" : "↓",
1771
+ " ",
1772
+ row.fit === "good" ? t("within range") : row.fit === "tight" ? t("may be snug") : t("may be loose")
1773
+ ] }) })
1774
+ ] }, i))
1775
+ ] })
1776
+ ] }),
1777
+ (!activeFit || activeFit.length === 0) && sizingResult.reasoning && /* @__PURE__ */ jsx("div", { className: "ps-tryon-sr-reasoning", children: /* @__PURE__ */ jsx("p", { children: sizingResult.reasoning }) }),
1778
+ /* @__PURE__ */ jsxs("div", { className: "ps-tryon-sr-ctas", children: [
1549
1779
  /* @__PURE__ */ jsxs("button", { className: "ps-tryon-cta", onClick: () => setView("upload"), children: [
1550
1780
  t("See how it looks on you"),
1551
1781
  " ",
@@ -2213,79 +2443,139 @@ const STYLES = `
2213
2443
  .ps-tryon-btn-retry { background: rgba(255,255,255,0.08); color: #fff; border: 1px solid #333 !important; }
2214
2444
  .ps-tryon-btn-retry:hover { background: rgba(255,255,255,0.12); }
2215
2445
 
2216
- /* Size recommendation */
2217
- .ps-tryon-size-recommend { margin-bottom: 0.83vw; }
2218
- .ps-tryon-size-title { font-size: 0.94vw; font-weight: 700; color: #fff; margin: 0 0 0.73vw; }
2219
- .ps-tryon-size-hero-row {
2220
- display: flex; align-items: center; gap: 0.83vw; padding: 0.83vw 1.04vw;
2221
- border: 1.5px solid #333; border-radius: 0.73vw; margin-bottom: 0.94vw; background: rgba(255,255,255,0.02);
2222
- }
2223
- .ps-tryon-size-badge {
2224
- display: inline-flex; align-items: center; justify-content: center;
2225
- min-width: 2.92vw; height: 2.92vw; padding: 0 0.63vw; border-radius: 0.63vw;
2226
- background: linear-gradient(135deg, #bb945c, #d6ba7d);
2227
- color: #111; font-size: 1.25vw; font-weight: 800; letter-spacing: -0.02em;
2228
- }
2229
- .ps-tryon-size-conf-label { font-size: 0.78vw; font-weight: 600; }
2230
- .ps-conf-high { color: #4ade80; } .ps-conf-medium { color: #bb945c; } .ps-conf-low { color: #ef4444; }
2231
-
2232
- .ps-tryon-sizing-loading { text-align: center; padding: 1.04vw 0; }
2446
+ /* ── Size Result (redesigned) ── */
2447
+ .ps-tryon-sr { display: flex; flex-direction: column; gap: 1.1vw; }
2448
+ .ps-tryon-sr-loading { text-align: center; padding: 2vw 0; }
2449
+ .ps-tryon-sr-loading p { font-size: 0.83vw; color: #999; margin-top: 0.5vw; }
2233
2450
  .ps-tryon-size-loading-spinner {
2234
- width: 1.88vw; height: 1.88vw; border: 3px solid #333;
2451
+ width: 2vw; height: 2vw; border: 3px solid #333;
2235
2452
  border-top-color: #bb945c; border-radius: 50%;
2236
- animation: ps-spin 0.8s linear infinite; margin: 0 auto 0.63vw;
2453
+ animation: ps-spin 0.8s linear infinite; margin: 0 auto;
2237
2454
  }
2238
2455
  @keyframes ps-spin { to { transform: rotate(360deg); } }
2239
2456
 
2240
- /* Fit Summary */
2241
- .ps-tryon-fit-summary { margin-bottom: 0.83vw; }
2242
- .ps-tryon-fit-summary-title { font-size: 0.78vw; font-weight: 700; color: #fff; margin: 0 0 0.52vw; }
2243
- .ps-tryon-fit-row { display: flex; align-items: center; gap: 0.52vw; padding: 0.42vw 0; }
2244
- .ps-tryon-fit-icon {
2245
- width: 1.15vw; height: 1.15vw; border-radius: 50%; display: flex; align-items: center; justify-content: center;
2246
- font-size: 0.63vw; font-weight: 700; flex-shrink: 0;
2247
- }
2248
- .ps-fit-icon-good { background: rgba(74,222,128,0.15); color: #4ade80; }
2249
- .ps-fit-icon-tight { background: rgba(245,158,11,0.15); color: #f59e0b; }
2250
- .ps-fit-icon-loose { background: rgba(96,165,250,0.15); color: #60a5fa; }
2251
- .ps-tryon-fit-text { font-size: 0.73vw; color: #ccc; line-height: 1.4; }
2252
- .ps-tryon-fit-text strong { color: #fff; font-weight: 600; }
2253
- .ps-tryon-fit-details-toggle {
2254
- display: inline-block; margin-top: 0.42vw; font-size: 0.68vw; color: #bb945c; cursor: pointer;
2255
- font-weight: 600; background: none; border: none; padding: 0; font-family: inherit;
2256
- }
2257
- .ps-tryon-fit-details-toggle:hover { color: #d6ba7d; }
2258
- .ps-tryon-fit-table { width: 100%; border-collapse: collapse; margin-top: 0.52vw; font-size: 0.68vw; }
2259
- .ps-tryon-fit-table th { text-align: left; padding: 0.42vw 0.52vw; border-bottom: 1px solid #333; color: #999; font-weight: 600; }
2260
- .ps-tryon-fit-table td { padding: 0.42vw 0.52vw; border-bottom: 1px solid #222; color: #ccc; }
2261
- .ps-fit-good { color: #4ade80; } .ps-fit-tight { color: #f59e0b; } .ps-fit-loose { color: #60a5fa; }
2457
+ /* Hero */
2458
+ .ps-tryon-sr-hero {
2459
+ display: flex; align-items: center; gap: 1vw; padding: 1vw 1.2vw;
2460
+ background: linear-gradient(135deg, rgba(187,148,92,0.08), rgba(187,148,92,0.02));
2461
+ border: 1.5px solid rgba(187,148,92,0.25); border-radius: 0.83vw;
2462
+ }
2463
+ .ps-tryon-sr-hero-badge {
2464
+ min-width: 3.5vw; height: 3.5vw; display: flex; align-items: center; justify-content: center;
2465
+ background: linear-gradient(135deg, #bb945c, #d6ba7d); color: #111;
2466
+ font-size: 1.5vw; font-weight: 800; border-radius: 0.73vw; letter-spacing: -0.02em;
2467
+ padding: 0 0.8vw; box-shadow: 0 0.4vw 1.2vw rgba(187,148,92,0.25);
2468
+ }
2469
+ .ps-tryon-sr-hero-info { flex: 1; }
2470
+ .ps-tryon-sr-hero-title { font-size: 1.04vw; font-weight: 700; color: #fff; margin-bottom: 0.15vw; }
2471
+ .ps-tryon-sr-hero-conf { font-size: 0.78vw; font-weight: 600; }
2472
+ .ps-conf-high { color: #4ade80; } .ps-conf-medium { color: #bb945c; } .ps-conf-low { color: #ef4444; }
2473
+
2474
+ /* International sizes */
2475
+ .ps-tryon-sr-intl { }
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; }
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; }
2262
2487
 
2263
- /* Equivalent Sizes */
2264
- .ps-tryon-equiv-section { margin-bottom: 0.83vw; }
2265
- .ps-tryon-equiv-title { font-size: 0.78vw; font-weight: 700; color: #fff; margin: 0 0 0.52vw; }
2266
- .ps-tryon-equiv-chips { display: flex; flex-wrap: wrap; gap: 0.42vw; }
2267
- .ps-tryon-equiv-chip {
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
+
2497
+ .ps-tryon-sr-intl-grid { display: flex; flex-wrap: wrap; gap: 0.42vw; }
2498
+ .ps-tryon-sr-intl-item {
2268
2499
  display: flex; align-items: center; border: 1.5px solid #333; border-radius: 0.52vw; overflow: hidden;
2269
2500
  }
2270
- .ps-tryon-equiv-region { padding: 0.36vw 0.52vw; font-size: 0.63vw; color: #999; font-weight: 600; background: rgba(255,255,255,0.03); }
2271
- .ps-tryon-equiv-value { padding: 0.36vw 0.63vw; font-size: 0.73vw; color: #fff; font-weight: 700; }
2501
+ .ps-tryon-sr-intl-code { padding: 0.42vw 0.57vw; font-size: 0.68vw; color: #999; font-weight: 700; background: rgba(255,255,255,0.03); letter-spacing: 0.04em; }
2502
+ .ps-tryon-sr-intl-val { padding: 0.42vw 0.68vw; font-size: 0.83vw; color: #fff; font-weight: 700; }
2272
2503
 
2273
- /* Multi-section garment sizing (tuxedo/set) */
2274
- .ps-tryon-multi-section { margin-bottom: 0.83vw; }
2275
- .ps-tryon-section-row { display: flex; align-items: center; justify-content: space-between; padding: 0.52vw 0; border-bottom: 1px solid #222; }
2276
- .ps-tryon-section-row:last-child { border-bottom: none; }
2277
- .ps-tryon-section-name { font-size: 0.78vw; color: #ccc; font-weight: 500; }
2278
- .ps-tryon-section-badge { font-size: 0.83vw; min-width: auto; padding: 0.26vw 0.83vw; }
2504
+ /* Size selector */
2505
+ .ps-tryon-sr-sizes { }
2506
+ .ps-tryon-sr-size-row { display: flex; flex-wrap: wrap; gap: 0.36vw; }
2507
+ .ps-tryon-sr-size-chip {
2508
+ position: relative; min-width: 2.6vw; height: 2.4vw; padding: 0 0.73vw;
2509
+ display: flex; align-items: center; justify-content: center;
2510
+ border: 1.5px solid #333; border-radius: 0.52vw; background: transparent;
2511
+ color: #999; font-size: 0.83vw; font-weight: 600; cursor: pointer;
2512
+ transition: all 0.2s; font-family: inherit;
2513
+ }
2514
+ .ps-tryon-sr-size-chip:hover { border-color: #555; color: #ccc; }
2515
+ .ps-tryon-sr-size-chip.ps-active {
2516
+ border-color: #bb945c; background: #bb945c; color: #111;
2517
+ box-shadow: 0 0.2vw 0.8vw rgba(187,148,92,0.2);
2518
+ }
2519
+ .ps-tryon-sr-size-chip.ps-recommended { border-color: rgba(187,148,92,0.4); }
2520
+ .ps-tryon-sr-rec-dot {
2521
+ position: absolute; bottom: -0.26vw; left: 50%; transform: translateX(-50%);
2522
+ width: 0.31vw; height: 0.31vw; border-radius: 50%; background: #bb945c;
2523
+ }
2524
+ .ps-tryon-sr-size-chip.ps-active .ps-tryon-sr-rec-dot { background: #111; }
2525
+ .ps-tryon-sr-comparing { font-size: 0.73vw; color: #bb945c; margin-top: 0.42vw; }
2526
+ .ps-tryon-sr-comparing strong { color: #d6ba7d; }
2527
+
2528
+ /* Fit analysis table */
2529
+ .ps-tryon-sr-fit { }
2530
+ .ps-tryon-sr-fit-table { border: 1.5px solid #333; border-radius: 0.73vw; overflow: hidden; }
2531
+ .ps-tryon-sr-fit-header {
2532
+ display: flex; padding: 0.57vw 0.83vw; background: #1a1b1a; border-bottom: 1px solid #333;
2533
+ }
2534
+ .ps-tryon-sr-fit-header span {
2535
+ font-size: 0.68vw; font-weight: 700; color: #666; text-transform: uppercase; letter-spacing: 0.06em;
2536
+ }
2537
+ .ps-tryon-sr-fit-row {
2538
+ display: flex; align-items: center; padding: 0.57vw 0.83vw;
2539
+ border-bottom: 1px solid #222; transition: background 0.15s;
2540
+ }
2541
+ .ps-tryon-sr-fit-row:last-child { border-bottom: none; }
2542
+ .ps-tryon-sr-fit-row:hover { background: rgba(255,255,255,0.02); }
2543
+ .ps-tryon-sr-fit-row.ps-fit-good { border-left: 3px solid #4ade80; }
2544
+ .ps-tryon-sr-fit-row.ps-fit-tight { border-left: 3px solid #f59e0b; }
2545
+ .ps-tryon-sr-fit-row.ps-fit-loose { border-left: 3px solid #60a5fa; }
2279
2546
 
2280
- .ps-tryon-size-reasoning { font-size: 0.73vw; color: #ccc; line-height: 1.6; margin-bottom: 0.73vw; }
2547
+ .ps-tryon-sr-fit-col-area { flex: 0 0 28%; font-size: 0.78vw; font-weight: 600; color: #fff; }
2548
+ .ps-tryon-sr-fit-col-you { flex: 0 0 22%; }
2549
+ .ps-tryon-sr-fit-col-chart { flex: 0 0 22%; font-size: 0.78vw; color: #999; font-weight: 500; }
2550
+ .ps-tryon-sr-fit-col-fit { flex: 1; text-align: right; }
2281
2551
 
2282
- /* Size Result View (standalone sizing result before try-on) */
2283
- .ps-tryon-size-result-view { display: flex; flex-direction: column; gap: 1vw; }
2284
- .ps-tryon-tryon-cta { display: flex; flex-direction: column; gap: 0.7vw; margin-top: 0.7vw; }
2552
+ .ps-tryon-sr-fit-input {
2553
+ width: 3.5vw; padding: 0.26vw 0.42vw; border: 1.5px solid #333; border-radius: 0.36vw;
2554
+ background: #111211; color: #fff; font-size: 0.78vw; font-weight: 600; font-family: inherit;
2555
+ outline: none; text-align: center; -moz-appearance: textfield;
2556
+ }
2557
+ .ps-tryon-sr-fit-input::-webkit-outer-spin-button,
2558
+ .ps-tryon-sr-fit-input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
2559
+ .ps-tryon-sr-fit-input:focus { border-color: #bb945c; }
2560
+
2561
+ .ps-tryon-sr-fit-badge {
2562
+ display: inline-flex; align-items: center; gap: 0.26vw; padding: 0.21vw 0.52vw;
2563
+ border-radius: 0.36vw; font-size: 0.63vw; font-weight: 700; white-space: nowrap;
2564
+ }
2565
+ .ps-tryon-sr-fit-badge.ps-fit-good { background: rgba(74,222,128,0.1); color: #4ade80; }
2566
+ .ps-tryon-sr-fit-badge.ps-fit-tight { background: rgba(245,158,11,0.1); color: #f59e0b; }
2567
+ .ps-tryon-sr-fit-badge.ps-fit-loose { background: rgba(96,165,250,0.1); color: #60a5fa; }
2568
+
2569
+ /* Reasoning fallback */
2570
+ .ps-tryon-sr-reasoning { padding: 0.83vw; border: 1px solid #333; border-radius: 0.63vw; background: #1a1b1a; }
2571
+ .ps-tryon-sr-reasoning p { font-size: 0.78vw; color: #ccc; line-height: 1.6; margin: 0; }
2572
+
2573
+ /* CTAs */
2574
+ .ps-tryon-sr-ctas { display: flex; flex-direction: column; gap: 0.52vw; margin-top: 0.36vw; }
2285
2575
  .ps-tryon-btn-secondary {
2286
2576
  background: transparent; border: 1.5px solid #444; color: #999; font-size: 0.83vw;
2287
- font-weight: 600; padding: 0.7vw 1.3vw; border-radius: 0.52vw;
2288
- cursor: pointer; transition: all 0.2s;
2577
+ font-weight: 600; padding: 0.73vw 1.3vw; border-radius: 0.52vw;
2578
+ cursor: pointer; transition: all 0.2s; font-family: inherit;
2289
2579
  }
2290
2580
  .ps-tryon-btn-secondary:hover { border-color: #666; color: #ccc; }
2291
2581
  .ps-tryon-size-compact { padding: 0.5vw 0; }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@primestyleai/tryon",
3
- "version": "3.14.0",
3
+ "version": "3.16.0",
4
4
  "description": "PrimeStyle Virtual Try-On SDK — React component & Web Component",
5
5
  "type": "module",
6
6
  "main": "dist/primestyle-tryon.js",