@primestyleai/tryon 3.17.0 → 3.19.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.
@@ -305,6 +305,12 @@ const en = {
305
305
  "Size Chart": "Size Chart",
306
306
  "tap to compare": "tap to compare",
307
307
  "Comparing size": "Comparing size",
308
+ "Compare with another size": "Compare with another size",
309
+ "recommended": "recommended",
310
+ "size": "size",
311
+ "Your size in other countries": "Your size in other countries",
312
+ "Showing fit for size": "Showing fit for size",
313
+ "Back to": "Back to",
308
314
  "Fit Analysis": "Fit Analysis",
309
315
  "Show more": "Show more",
310
316
  "Done": "Done",
@@ -1,5 +1,5 @@
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";
1
+ import { c as createT, A as ApiClient, S as SseClient, i as isValidImageFile, a as compressImage } from "./index-DmAF7P54.js";
2
+ import { P, b, T, d, r } from "./index-DmAF7P54.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-DvAzTRFF.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-DmAF7P54.js";
5
5
  const HEADER_ALIASES = {
6
6
  // ── Size label columns (skipped during field derivation) ──
7
7
  size: "__size__",
@@ -424,9 +424,6 @@ function detectLocale() {
424
424
  function isImperial(locale) {
425
425
  return locale === "US" || locale === "UK";
426
426
  }
427
- function cmToIn(cm) {
428
- return +(cm / 2.54).toFixed(1);
429
- }
430
427
  function inToCm(inches) {
431
428
  return +(inches * 2.54).toFixed(1);
432
429
  }
@@ -826,115 +823,7 @@ function PrimeStyleTryonInner({
826
823
  }
827
824
  return formGender === "female" ? FALLBACK_FIELDS_FEMALE : FALLBACK_FIELDS_MALE;
828
825
  }, [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
- let idx = sizeGuide.headers.findIndex((h) => /^size$/i.test(h.trim()));
864
- if (idx < 0) idx = sizeGuide.headers.findIndex((h) => /size|taglia|größe|taille/i.test(h.trim()));
865
- if (idx < 0) {
866
- const firstColVals = sizeGuide.rows.map((r) => r[0]?.trim() || "");
867
- idx = firstColVals.some((v) => /^(XXS|XS|S|M|L|XL|XXL|XXXL|\d{1,3})$/i.test(v)) ? 0 : 0;
868
- }
869
- const colMap = [];
870
- const intlCols = [];
871
- sizeGuide.headers.forEach((h, i) => {
872
- if (i === idx) return;
873
- const lower = h.toLowerCase().trim().replace(/\s*\(.*\)/, "");
874
- const clean = lower.replace(/\s*size\s*/gi, "").trim();
875
- if (INTL.has(clean)) {
876
- intlCols.push({ hi: i, code: clean.toUpperCase() });
877
- return;
878
- }
879
- let fk = HEADER_MAP[clean];
880
- if (!fk) {
881
- const pk = Object.keys(HEADER_MAP).find((k) => clean.includes(k));
882
- if (pk) fk = HEADER_MAP[pk];
883
- }
884
- if (fk && userMeas[fk] !== void 0) colMap.push({ hi: i, formKey: fk, label: h.trim() });
885
- });
886
- if (colMap.length === 0) return null;
887
- const parseRange = (cell) => {
888
- const nums = cell.replace(/[^\d.\-–]/g, " ").trim().split(/[\s\-–]+/).filter(Boolean).map(Number).filter((n) => !isNaN(n));
889
- if (nums.length === 0) return null;
890
- return { min: Math.min(...nums), max: Math.max(...nums) };
891
- };
892
- const scores = [];
893
- for (const row of sizeGuide.rows) {
894
- const label = row[idx] || "";
895
- let fitting = 0;
896
- let total = 0;
897
- let dist = 0;
898
- const details = [];
899
- for (const col of colMap) {
900
- const range = parseRange(row[col.hi] || "");
901
- if (!range) continue;
902
- total++;
903
- const uv = userMeas[col.formKey];
904
- const fit = uv >= range.min && uv <= range.max ? "good" : uv < range.min ? "tight" : "loose";
905
- if (fit === "good") fitting++;
906
- dist += Math.abs(uv - (range.min + range.max) / 2);
907
- const dispVal = sizingUnit === "in" ? `${cmToIn(uv)} in` : `${Math.round(uv)} cm`;
908
- const dispRange = sizingUnit === "in" ? `${cmToIn(range.min)}–${cmToIn(range.max)} in` : `${Math.round(range.min)}–${Math.round(range.max)} cm`;
909
- details.push({ measurement: col.label, userValue: dispVal, chartRange: dispRange, fit });
910
- }
911
- if (total === 0) continue;
912
- const intl = {};
913
- for (const ic of intlCols) {
914
- if (row[ic.hi]) intl[ic.code] = row[ic.hi];
915
- }
916
- scores.push({ label, fitting, total, dist, details, intl, row });
917
- }
918
- if (scores.length === 0) return null;
919
- scores.sort((a, b) => b.fitting - a.fitting || a.dist - b.dist);
920
- const best = scores[0];
921
- const conf = best.fitting === best.total ? "high" : best.fitting >= best.total * 0.6 ? "medium" : "low";
922
- return {
923
- recommendedSize: best.label,
924
- confidence: conf,
925
- reasoning: `Based on your measurements, size ${best.label} is the best fit.`,
926
- internationalSizes: best.intl,
927
- matchDetails: best.details,
928
- method: "deterministic"
929
- };
930
- }, [sizingMethod, sizeGuide, dynamicFields, sizingUnit]);
931
826
  const submitSizing = useCallback(async () => {
932
- const localResult = computeSizingLocally();
933
- if (localResult) {
934
- setSizingResult(localResult);
935
- setSizingLoading(false);
936
- return;
937
- }
938
827
  if (!apiRef.current) return;
939
828
  const baseUrl = getApiUrl(apiUrl);
940
829
  const key = getApiKey();
@@ -993,7 +882,7 @@ function PrimeStyleTryonInner({
993
882
  } finally {
994
883
  setSizingLoading(false);
995
884
  }
996
- }, [apiUrl, sizingMethod, sizingCountry, heightUnit, weightUnit, sizingUnit, sizeGuide, productTitle, dynamicFields, computeSizingLocally]);
885
+ }, [apiUrl, sizingMethod, sizingCountry, heightUnit, weightUnit, sizingUnit, sizeGuide, productTitle, dynamicFields]);
997
886
  const handleTryOnSubmit = useCallback(async () => {
998
887
  if (!selectedFile || !apiRef.current || !sseRef.current) {
999
888
  const msg = !apiRef.current ? t("SDK not configured. Please provide an API key.") : t("Something went wrong");
@@ -1582,108 +1471,65 @@ function PrimeStyleTryonInner({
1582
1471
  ] }, `form-${formGender}-${sizingUnit}-${heightUnit}-${sizingCountry}-${formKey}`);
1583
1472
  }
1584
1473
  function SizeResultView() {
1585
- const [activeSize, setActiveSize] = useState(sizingResult?.recommendedSize || "");
1586
- const [editedValues, setEditedValues] = useState({});
1587
- const isCmResult = sizingUnit === "cm";
1588
- const chartData = useMemo(() => {
1589
- if (!sizeGuide?.found || !sizeGuide.headers || !sizeGuide.rows) return null;
1590
- const INTL = /* @__PURE__ */ new Set(["eu", "us", "uk", "it", "fr", "de", "jp", "cn", "kr", "au", "br", "eur"]);
1591
- const MEAS_MAP = {
1592
- chest: "chest",
1593
- bust: "bust",
1594
- waist: "waist",
1595
- hips: "hips",
1596
- hip: "hips",
1597
- shoulder: "shoulderWidth",
1598
- shoulders: "shoulderWidth",
1599
- "shoulder width": "shoulderWidth",
1600
- sleeve: "sleeveLength",
1601
- "sleeve length": "sleeveLength",
1602
- inseam: "inseam",
1603
- neck: "neckCircumference",
1604
- "neck circumference": "neckCircumference",
1605
- foot: "footLengthCm",
1606
- "foot length": "footLengthCm"
1607
- };
1608
- let idx = sizeGuide.headers.findIndex((h) => /^size$/i.test(h.trim()));
1609
- if (idx < 0) idx = sizeGuide.headers.findIndex((h) => /size|taglia|größe|taille/i.test(h.trim()));
1610
- if (idx < 0) {
1611
- const firstColVals = sizeGuide.rows.map((r) => r[0]?.trim() || "");
1612
- const looksLikeSize = firstColVals.some((v) => /^(XXS|XS|S|M|L|XL|XXL|XXXL|\d{1,3})$/i.test(v));
1613
- idx = looksLikeSize ? 0 : -1;
1474
+ const unitLbl = sizingUnit === "cm" ? t("cm") : t("in");
1475
+ const [editVals, setEditVals] = useState({});
1476
+ const [compareSize, setCompareSize] = useState("");
1477
+ const sizeColIdx = useMemo(() => {
1478
+ if (!sizeGuide?.headers || !sizeGuide?.rows) return -1;
1479
+ const byName = sizeGuide.headers.findIndex((h) => /size|taglia|größe|taille/i.test(h.trim()));
1480
+ if (byName >= 0) return byName;
1481
+ for (let c = 0; c < sizeGuide.headers.length; c++) {
1482
+ const vals = sizeGuide.rows.map((r) => r[c]?.trim() || "");
1483
+ if (vals.some((v) => /^(XXS|XS|S|M|L|XL|XXL|XXXL|ONE SIZE|\d{1,2})$/i.test(v))) return c;
1614
1484
  }
1615
- if (idx < 0) idx = 0;
1616
- const measCols = [];
1617
- const intlCols = [];
1618
- sizeGuide.headers.forEach((h, i) => {
1619
- if (i === idx) return;
1620
- const lower = h.toLowerCase().trim().replace(/\s*\(.*\)/, "");
1621
- const clean = lower.replace(/\s*size\s*/gi, "").trim();
1622
- if (INTL.has(clean)) {
1623
- intlCols.push({ headerIdx: i, code: clean.toUpperCase() });
1624
- } else if (MEAS_MAP[clean]) {
1625
- measCols.push({ headerIdx: i, label: h.trim(), formKey: MEAS_MAP[clean] });
1626
- } else if (clean && clean !== "size") {
1627
- const partialKey = Object.keys(MEAS_MAP).find((k) => clean.includes(k));
1628
- const fk = partialKey ? MEAS_MAP[partialKey] : clean.replace(/\s+/g, "");
1629
- measCols.push({ headerIdx: i, label: h.trim(), formKey: fk });
1630
- }
1485
+ return 0;
1486
+ }, [sizeGuide]);
1487
+ const allSizes = useMemo(() => {
1488
+ if (sizeColIdx < 0 || !sizeGuide?.rows) return [];
1489
+ return sizeGuide.rows.map((r) => r[sizeColIdx]?.trim()).filter(Boolean);
1490
+ }, [sizeGuide, sizeColIdx]);
1491
+ const pNum = (s) => {
1492
+ const n = parseFloat(s.replace(/[^\d.]/g, ""));
1493
+ return isNaN(n) ? 0 : n;
1494
+ };
1495
+ const pRange = (s) => {
1496
+ const ns = s.replace(/[^\d.\-–]/g, " ").trim().split(/[\s\-–]+/).filter(Boolean).map(Number).filter((n) => !isNaN(n));
1497
+ return ns.length ? { min: Math.min(...ns), max: Math.max(...ns) } : { min: 0, max: 0 };
1498
+ };
1499
+ const chartRangeFor = useCallback((measurement, size) => {
1500
+ if (!sizeGuide?.headers || !sizeGuide?.rows || sizeColIdx < 0) return null;
1501
+ const mc = sizeGuide.headers.findIndex((h) => {
1502
+ const a = h.toLowerCase().trim(), b = measurement.toLowerCase().trim();
1503
+ return a === b || a.includes(b) || b.includes(a);
1631
1504
  });
1632
- const sizes = sizeGuide.rows.map((row) => {
1633
- const label = row[idx] || "";
1634
- const measurements = {};
1635
- for (const col of measCols) {
1636
- const cell = row[col.headerIdx] || "";
1637
- const nums = cell.replace(/[^\d.\-–]/g, " ").trim().split(/[\s\-–]+/).filter(Boolean).map(Number).filter((n) => !isNaN(n));
1638
- if (nums.length > 0) {
1639
- measurements[col.formKey] = { raw: cell, min: Math.min(...nums), max: Math.max(...nums) };
1505
+ if (mc < 0) return null;
1506
+ const row = sizeGuide.rows.find((r) => r[sizeColIdx]?.trim() === size);
1507
+ return row?.[mc] ? { range: row[mc], ...pRange(row[mc]) } : null;
1508
+ }, [sizeGuide, sizeColIdx]);
1509
+ const fitRows = useMemo(() => {
1510
+ if (!sizingResult?.matchDetails?.length) return [];
1511
+ compareSize || sizingResult.recommendedSize || "";
1512
+ return sizingResult.matchDetails.map((m) => {
1513
+ const origNum = pNum(m.userValue);
1514
+ const edited = editVals[m.measurement];
1515
+ const userNum = edited !== void 0 && edited !== "" ? parseFloat(edited) : origNum;
1516
+ let { min: rMin, max: rMax } = pRange(m.chartRange);
1517
+ let chartLabel = m.chartRange;
1518
+ if (compareSize && compareSize !== sizingResult.recommendedSize) {
1519
+ const alt = chartRangeFor(m.measurement, compareSize);
1520
+ if (alt) {
1521
+ chartLabel = alt.range;
1522
+ rMin = alt.min;
1523
+ rMax = alt.max;
1640
1524
  }
1641
1525
  }
1642
- const intl = {};
1643
- for (const col of intlCols) {
1644
- if (row[col.headerIdx]) intl[col.code] = row[col.headerIdx];
1645
- }
1646
- return { label, measurements, intl };
1526
+ const fit = userNum >= rMin && userNum <= rMax ? "good" : userNum < rMin ? "tight" : "loose";
1527
+ return { area: m.measurement, userNum, chartLabel, fit };
1647
1528
  });
1648
- return { sizes: sizes.filter((s) => s.label.trim()), measCols, intlCols };
1649
- }, [sizeGuide]);
1650
- const getUserVal = useCallback((formKey2) => {
1651
- const edited = editedValues[formKey2];
1652
- if (edited !== void 0 && edited !== "") {
1653
- const v2 = parseFloat(edited);
1654
- return isNaN(v2) ? null : isCmResult ? v2 : inToCm(v2);
1655
- }
1656
- const raw = formRef.current[formKey2];
1657
- if (!raw) return null;
1658
- const v = parseFloat(raw);
1659
- return isNaN(v) ? null : isCmResult ? v : inToCm(v);
1660
- }, [editedValues, isCmResult]);
1661
- const activeFit = useMemo(() => {
1662
- if (!chartData) return null;
1663
- const sizeRow = chartData.sizes.find((s) => s.label === activeSize);
1664
- if (!sizeRow) return null;
1665
- const details = [];
1666
- for (const col of chartData.measCols) {
1667
- const range = sizeRow.measurements[col.formKey];
1668
- if (!range) continue;
1669
- const userVal = getUserVal(col.formKey);
1670
- if (userVal === null) continue;
1671
- const fit = userVal >= range.min && userVal <= range.max ? "good" : userVal < range.min ? "tight" : "loose";
1672
- details.push({ label: col.label, formKey: col.formKey, userVal, chartMin: range.min, chartMax: range.max, chartRaw: range.raw, fit });
1673
- }
1674
- return details;
1675
- }, [chartData, activeSize, getUserVal]);
1676
- const activeIntl = useMemo(() => {
1677
- if (!chartData) return sizingResult?.internationalSizes || {};
1678
- const sizeRow = chartData.sizes.find((s) => s.label === activeSize);
1679
- return sizeRow?.intl || sizingResult?.internationalSizes || {};
1680
- }, [chartData, activeSize, sizingResult]);
1681
- const fmtRange = (min, max) => {
1682
- if (isCmResult) return min === max ? `${Math.round(min)}` : `${Math.round(min)}–${Math.round(max)}`;
1683
- return min === max ? `${cmToIn(min)}` : `${cmToIn(min)}–${cmToIn(max)}`;
1684
- };
1685
- const unitLabel = isCmResult ? t("cm") : t("in");
1686
- const isRecommended = activeSize === sizingResult?.recommendedSize;
1529
+ }, [sizingResult, compareSize, editVals, chartRangeFor]);
1530
+ const intlSizes = sizingResult?.internationalSizes || {};
1531
+ const recSize = sizingResult?.recommendedSize || "";
1532
+ const selSize = compareSize || recSize;
1687
1533
  return /* @__PURE__ */ jsxs("div", { className: "ps-tryon-sr", children: [
1688
1534
  sizingLoading && !sizingResult && /* @__PURE__ */ jsxs("div", { className: "ps-tryon-sr-loading", children: [
1689
1535
  /* @__PURE__ */ jsx("div", { className: "ps-tryon-size-loading-spinner" }),
@@ -1691,129 +1537,75 @@ function PrimeStyleTryonInner({
1691
1537
  ] }),
1692
1538
  sizingResult && /* @__PURE__ */ jsxs(Fragment, { children: [
1693
1539
  /* @__PURE__ */ jsxs("div", { className: "ps-tryon-sr-hero", children: [
1694
- /* @__PURE__ */ jsx("div", { className: "ps-tryon-sr-hero-badge", children: sizingResult.recommendedSize }),
1540
+ recSize && /* @__PURE__ */ jsx("div", { className: "ps-tryon-sr-hero-badge", children: recSize }),
1695
1541
  /* @__PURE__ */ jsxs("div", { className: "ps-tryon-sr-hero-info", children: [
1696
- /* @__PURE__ */ jsx("div", { className: "ps-tryon-sr-hero-title", children: t("Your Size") }),
1542
+ /* @__PURE__ */ jsxs("div", { className: "ps-tryon-sr-hero-title", children: [
1543
+ t("Your Size"),
1544
+ recSize ? `: ${recSize}` : ""
1545
+ ] }),
1697
1546
  /* @__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") })
1698
1547
  ] })
1699
1548
  ] }),
1700
- Object.keys(activeIntl).length > 0 && (() => {
1701
- const PRIMARY_CODES = ["US", "UK", "EU", "IT", "FR", "DE"];
1702
- const primary = PRIMARY_CODES.filter((c) => activeIntl[c]).map((c) => ({ code: c, val: activeIntl[c] }));
1703
- const rest = Object.entries(activeIntl).filter(([c]) => !PRIMARY_CODES.includes(c)).map(([code, val]) => ({ code, val }));
1704
- return /* @__PURE__ */ jsxs("div", { className: "ps-tryon-sr-intl", children: [
1705
- /* @__PURE__ */ jsx("div", { className: "ps-tryon-sr-label", children: t("Equivalent Sizes") }),
1706
- 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: [
1707
- /* @__PURE__ */ jsx("span", { className: "ps-tryon-sr-intl-card-val", children: s.val }),
1708
- /* @__PURE__ */ jsx("span", { className: "ps-tryon-sr-intl-card-code", children: s.code })
1709
- ] }, s.code)) }),
1710
- rest.length > 0 && /* @__PURE__ */ jsxs("details", { className: "ps-tryon-sr-intl-more", children: [
1711
- /* @__PURE__ */ jsxs("summary", { className: "ps-tryon-sr-intl-more-btn", children: [
1712
- t("Show more"),
1713
- " (",
1714
- rest.length,
1715
- ")"
1716
- ] }),
1717
- /* @__PURE__ */ jsx("div", { className: "ps-tryon-sr-intl-grid", children: rest.map((s) => /* @__PURE__ */ jsxs("div", { className: "ps-tryon-sr-intl-item", children: [
1718
- /* @__PURE__ */ jsx("span", { className: "ps-tryon-sr-intl-code", children: s.code }),
1719
- /* @__PURE__ */ jsx("span", { className: "ps-tryon-sr-intl-val", children: s.val })
1720
- ] }, s.code)) })
1721
- ] })
1722
- ] });
1723
- })(),
1724
- chartData && chartData.sizes.length > 0 && /* @__PURE__ */ jsxs("div", { className: "ps-tryon-sr-sizes", children: [
1725
- /* @__PURE__ */ jsxs("div", { className: "ps-tryon-sr-label", children: [
1726
- t("Size Chart"),
1549
+ Object.keys(intlSizes).length > 0 && /* @__PURE__ */ jsxs("div", { className: "ps-tryon-sr-intl", children: [
1550
+ /* @__PURE__ */ jsx("div", { className: "ps-tryon-sr-label", children: t("Your size in other countries") }),
1551
+ /* @__PURE__ */ jsx("div", { className: "ps-tryon-sr-intl-primary", children: Object.entries(intlSizes).map(([code, val]) => /* @__PURE__ */ jsxs("div", { className: "ps-tryon-sr-intl-card", children: [
1552
+ /* @__PURE__ */ jsx("span", { className: "ps-tryon-sr-intl-card-val", children: val }),
1553
+ /* @__PURE__ */ jsx("span", { className: "ps-tryon-sr-intl-card-code", children: code })
1554
+ ] }, code)) })
1555
+ ] }),
1556
+ allSizes.length > 1 && /* @__PURE__ */ jsxs("div", { className: "ps-tryon-sr-compare", children: [
1557
+ /* @__PURE__ */ jsx("div", { className: "ps-tryon-sr-label", children: t("Compare with another size") }),
1558
+ /* @__PURE__ */ jsx("select", { className: "ps-tryon-sr-compare-select", value: selSize, onChange: (e) => setCompareSize(e.target.value), children: allSizes.map((s) => /* @__PURE__ */ jsxs("option", { value: s, children: [
1559
+ s,
1560
+ s === recSize ? ` ★ ${t("recommended")}` : ""
1561
+ ] }, s)) }),
1562
+ compareSize && compareSize !== recSize && /* @__PURE__ */ jsxs("div", { className: "ps-tryon-sr-compare-note", children: [
1563
+ t("Showing fit for size"),
1727
1564
  " ",
1728
- /* @__PURE__ */ jsxs("span", { className: "ps-tryon-sr-label-hint", children: [
1729
- " ",
1730
- t("tap to compare")
1565
+ /* @__PURE__ */ jsx("strong", { children: compareSize }),
1566
+ /* @__PURE__ */ jsxs("button", { className: "ps-tryon-sr-compare-reset", onClick: () => setCompareSize(""), children: [
1567
+ t("Back to"),
1568
+ " ",
1569
+ recSize
1731
1570
  ] })
1732
- ] }),
1733
- /* @__PURE__ */ jsx("div", { className: "ps-tryon-sr-size-row", children: chartData.sizes.map((s) => /* @__PURE__ */ jsxs(
1734
- "button",
1735
- {
1736
- onClick: () => setActiveSize(s.label),
1737
- className: `ps-tryon-sr-size-chip${s.label === activeSize ? " ps-active" : ""}${s.label === sizingResult.recommendedSize ? " ps-recommended" : ""}`,
1738
- children: [
1739
- s.label,
1740
- s.label === sizingResult.recommendedSize && /* @__PURE__ */ jsx("span", { className: "ps-tryon-sr-rec-dot" })
1741
- ]
1742
- },
1743
- s.label
1744
- )) }),
1745
- !isRecommended && /* @__PURE__ */ jsxs("div", { className: "ps-tryon-sr-comparing", children: [
1746
- t("Comparing size"),
1747
- " ",
1748
- /* @__PURE__ */ jsx("strong", { children: activeSize })
1749
1571
  ] })
1750
1572
  ] }),
1751
- (() => {
1752
- const hasDynamic = activeFit && activeFit.length > 0;
1753
- const hasBackend = sizingResult.matchDetails && sizingResult.matchDetails.length > 0;
1754
- if (hasDynamic) return /* @__PURE__ */ jsxs("div", { className: "ps-tryon-sr-fit", children: [
1755
- /* @__PURE__ */ jsx("div", { className: "ps-tryon-sr-label", children: t("Fit Analysis") }),
1756
- /* @__PURE__ */ jsxs("div", { className: "ps-tryon-sr-fit-table", children: [
1757
- /* @__PURE__ */ jsxs("div", { className: "ps-tryon-sr-fit-header", children: [
1758
- /* @__PURE__ */ jsx("span", { className: "ps-tryon-sr-fit-col-area", children: t("Area") }),
1759
- /* @__PURE__ */ jsxs("span", { className: "ps-tryon-sr-fit-col-you", children: [
1760
- t("You"),
1761
- " (",
1762
- unitLabel,
1763
- ")"
1764
- ] }),
1765
- /* @__PURE__ */ jsxs("span", { className: "ps-tryon-sr-fit-col-chart", children: [
1573
+ fitRows.length > 0 && /* @__PURE__ */ jsxs("div", { className: "ps-tryon-sr-fit", children: [
1574
+ /* @__PURE__ */ jsx("div", { className: "ps-tryon-sr-label", children: t("Fit Analysis") }),
1575
+ fitRows.map((row, i) => /* @__PURE__ */ jsxs("div", { className: `ps-tryon-sr-fit-card ps-fit-${row.fit}`, children: [
1576
+ /* @__PURE__ */ jsxs("div", { className: "ps-tryon-sr-fit-card-top", children: [
1577
+ /* @__PURE__ */ jsx("span", { className: "ps-tryon-sr-fit-area", children: row.area }),
1578
+ /* @__PURE__ */ jsx("span", { className: `ps-tryon-sr-fit-badge ps-fit-${row.fit}`, children: row.fit === "good" ? `✓ ${t("within range")}` : row.fit === "tight" ? `↑ ${t("may be snug")}` : `↓ ${t("may be loose")}` })
1579
+ ] }),
1580
+ /* @__PURE__ */ jsxs("div", { className: "ps-tryon-sr-fit-card-bottom", children: [
1581
+ /* @__PURE__ */ jsxs("div", { className: "ps-tryon-sr-fit-val", children: [
1582
+ /* @__PURE__ */ jsx("span", { className: "ps-tryon-sr-fit-val-label", children: t("You") }),
1583
+ /* @__PURE__ */ jsxs("div", { className: "ps-tryon-sr-fit-val-input-wrap", children: [
1584
+ /* @__PURE__ */ jsx(
1585
+ "input",
1586
+ {
1587
+ type: "number",
1588
+ className: "ps-tryon-sr-fit-input",
1589
+ value: editVals[row.area] !== void 0 ? editVals[row.area] : row.userNum,
1590
+ onChange: (e) => setEditVals((prev) => ({ ...prev, [row.area]: e.target.value }))
1591
+ }
1592
+ ),
1593
+ /* @__PURE__ */ jsx("span", { className: "ps-tryon-sr-fit-unit", children: unitLbl })
1594
+ ] })
1595
+ ] }),
1596
+ /* @__PURE__ */ jsxs("div", { className: "ps-tryon-sr-fit-val", children: [
1597
+ /* @__PURE__ */ jsxs("span", { className: "ps-tryon-sr-fit-val-label", children: [
1766
1598
  t("Chart"),
1767
1599
  " (",
1768
- unitLabel,
1600
+ selSize,
1769
1601
  ")"
1770
1602
  ] }),
1771
- /* @__PURE__ */ jsx("span", { className: "ps-tryon-sr-fit-col-fit", children: t("Fit") })
1772
- ] }),
1773
- activeFit.map((row, i) => /* @__PURE__ */ jsxs("div", { className: `ps-tryon-sr-fit-row ps-fit-${row.fit}`, children: [
1774
- /* @__PURE__ */ jsx("span", { className: "ps-tryon-sr-fit-col-area", children: row.label }),
1775
- /* @__PURE__ */ jsx("span", { className: "ps-tryon-sr-fit-col-you", children: /* @__PURE__ */ jsx(
1776
- "input",
1777
- {
1778
- type: "number",
1779
- className: "ps-tryon-sr-fit-input",
1780
- defaultValue: isCmResult ? Math.round(row.userVal) : cmToIn(row.userVal),
1781
- onBlur: (e) => setEditedValues((prev) => ({ ...prev, [row.formKey]: e.target.value }))
1782
- }
1783
- ) }),
1784
- /* @__PURE__ */ jsx("span", { className: "ps-tryon-sr-fit-col-chart", children: fmtRange(row.chartMin, row.chartMax) }),
1785
- /* @__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: [
1786
- row.fit === "good" ? "✓" : row.fit === "tight" ? "↑" : "↓",
1787
- " ",
1788
- row.fit === "good" ? t("within range") : row.fit === "tight" ? t("may be snug") : t("may be loose")
1789
- ] }) })
1790
- ] }, i))
1791
- ] })
1792
- ] });
1793
- if (hasBackend) return /* @__PURE__ */ jsxs("div", { className: "ps-tryon-sr-fit", children: [
1794
- /* @__PURE__ */ jsx("div", { className: "ps-tryon-sr-label", children: t("Fit Analysis") }),
1795
- /* @__PURE__ */ jsxs("div", { className: "ps-tryon-sr-fit-table", children: [
1796
- /* @__PURE__ */ jsxs("div", { className: "ps-tryon-sr-fit-header", children: [
1797
- /* @__PURE__ */ jsx("span", { className: "ps-tryon-sr-fit-col-area", children: t("Area") }),
1798
- /* @__PURE__ */ jsx("span", { className: "ps-tryon-sr-fit-col-you", children: t("You") }),
1799
- /* @__PURE__ */ jsx("span", { className: "ps-tryon-sr-fit-col-chart", children: t("Chart") }),
1800
- /* @__PURE__ */ jsx("span", { className: "ps-tryon-sr-fit-col-fit", children: t("Fit") })
1801
- ] }),
1802
- sizingResult.matchDetails.map((m, i) => /* @__PURE__ */ jsxs("div", { className: `ps-tryon-sr-fit-row ps-fit-${m.fit}`, children: [
1803
- /* @__PURE__ */ jsx("span", { className: "ps-tryon-sr-fit-col-area", children: m.measurement }),
1804
- /* @__PURE__ */ jsx("span", { className: "ps-tryon-sr-fit-col-you", children: m.userValue }),
1805
- /* @__PURE__ */ jsx("span", { className: "ps-tryon-sr-fit-col-chart", children: m.chartRange }),
1806
- /* @__PURE__ */ jsx("span", { className: "ps-tryon-sr-fit-col-fit", children: /* @__PURE__ */ jsxs("span", { className: `ps-tryon-sr-fit-badge ps-fit-${m.fit}`, children: [
1807
- m.fit === "good" ? "✓" : m.fit === "tight" ? "↑" : "↓",
1808
- " ",
1809
- m.fit === "good" ? t("within range") : m.fit === "tight" ? t("may be snug") : t("may be loose")
1810
- ] }) })
1811
- ] }, i))
1603
+ /* @__PURE__ */ jsx("span", { className: "ps-tryon-sr-fit-val-text", children: row.chartLabel })
1604
+ ] })
1812
1605
  ] })
1813
- ] });
1814
- if (sizingResult.reasoning) return /* @__PURE__ */ jsx("div", { className: "ps-tryon-sr-reasoning", children: /* @__PURE__ */ jsx("p", { children: sizingResult.reasoning }) });
1815
- return null;
1816
- })(),
1606
+ ] }, i))
1607
+ ] }),
1608
+ fitRows.length === 0 && sizingResult.reasoning && /* @__PURE__ */ jsx("div", { className: "ps-tryon-sr-reasoning", children: /* @__PURE__ */ jsx("p", { children: sizingResult.reasoning }) }),
1817
1609
  /* @__PURE__ */ jsxs("div", { className: "ps-tryon-sr-ctas", children: [
1818
1610
  /* @__PURE__ */ jsxs("button", { className: "ps-tryon-cta", onClick: () => setView("upload"), children: [
1819
1611
  t("See how it looks on you"),
@@ -2564,38 +2356,65 @@ const STYLES = `
2564
2356
  .ps-tryon-sr-comparing { font-size: 0.73vw; color: #bb945c; margin-top: 0.42vw; }
2565
2357
  .ps-tryon-sr-comparing strong { color: #d6ba7d; }
2566
2358
 
2567
- /* Fit analysis table */
2568
- .ps-tryon-sr-fit { }
2569
- .ps-tryon-sr-fit-table { border: 1.5px solid #333; border-radius: 0.73vw; overflow: hidden; }
2570
- .ps-tryon-sr-fit-header {
2571
- display: flex; padding: 0.57vw 0.83vw; background: #1a1b1a; border-bottom: 1px solid #333;
2572
- }
2573
- .ps-tryon-sr-fit-header span {
2574
- font-size: 0.68vw; font-weight: 700; color: #666; text-transform: uppercase; letter-spacing: 0.06em;
2575
- }
2576
- .ps-tryon-sr-fit-row {
2577
- display: flex; align-items: center; padding: 0.57vw 0.83vw;
2578
- border-bottom: 1px solid #222; transition: background 0.15s;
2579
- }
2580
- .ps-tryon-sr-fit-row:last-child { border-bottom: none; }
2581
- .ps-tryon-sr-fit-row:hover { background: rgba(255,255,255,0.02); }
2582
- .ps-tryon-sr-fit-row.ps-fit-good { border-left: 3px solid #4ade80; }
2583
- .ps-tryon-sr-fit-row.ps-fit-tight { border-left: 3px solid #f59e0b; }
2584
- .ps-tryon-sr-fit-row.ps-fit-loose { border-left: 3px solid #60a5fa; }
2585
-
2586
- .ps-tryon-sr-fit-col-area { flex: 0 0 28%; font-size: 0.78vw; font-weight: 600; color: #fff; }
2587
- .ps-tryon-sr-fit-col-you { flex: 0 0 22%; }
2588
- .ps-tryon-sr-fit-col-chart { flex: 0 0 22%; font-size: 0.78vw; color: #999; font-weight: 500; }
2589
- .ps-tryon-sr-fit-col-fit { flex: 1; text-align: right; }
2359
+ /* Fit analysis cards */
2360
+ .ps-tryon-sr-fit { display: flex; flex-direction: column; gap: 0.52vw; }
2361
+ .ps-tryon-sr-fit-card {
2362
+ border: 1.5px solid #333; border-radius: 0.63vw; overflow: hidden; transition: border-color 0.2s;
2363
+ }
2364
+ .ps-tryon-sr-fit-card.ps-fit-good { border-left: 3px solid #4ade80; }
2365
+ .ps-tryon-sr-fit-card.ps-fit-tight { border-left: 3px solid #f59e0b; }
2366
+ .ps-tryon-sr-fit-card.ps-fit-loose { border-left: 3px solid #60a5fa; }
2367
+ .ps-tryon-sr-fit-card-top {
2368
+ display: flex; align-items: center; justify-content: space-between;
2369
+ padding: 0.52vw 0.83vw; background: #1a1b1a;
2370
+ }
2371
+ .ps-tryon-sr-fit-area { font-size: 0.83vw; font-weight: 600; color: #fff; }
2372
+ .ps-tryon-sr-fit-card-bottom {
2373
+ display: flex; gap: 1vw; padding: 0.52vw 0.83vw;
2374
+ }
2375
+ .ps-tryon-sr-fit-val { flex: 1; }
2376
+ .ps-tryon-sr-fit-val-label { font-size: 0.57vw; font-weight: 700; color: #666; text-transform: uppercase; letter-spacing: 0.06em; margin-bottom: 0.21vw; display: block; }
2377
+ .ps-tryon-sr-fit-val-input-wrap { display: flex; align-items: center; gap: 0.26vw; }
2378
+ .ps-tryon-sr-fit-val-text { font-size: 0.83vw; font-weight: 600; color: #ccc; }
2590
2379
 
2591
2380
  .ps-tryon-sr-fit-input {
2592
- width: 3.5vw; padding: 0.26vw 0.42vw; border: 1.5px solid #333; border-radius: 0.36vw;
2593
- background: #111211; color: #fff; font-size: 0.78vw; font-weight: 600; font-family: inherit;
2381
+ width: 4vw; padding: 0.31vw 0.42vw; border: 1.5px solid #444; border-radius: 0.36vw;
2382
+ background: #0c0c0d; color: #fff; font-size: 0.83vw; font-weight: 600; font-family: inherit;
2594
2383
  outline: none; text-align: center; -moz-appearance: textfield;
2595
2384
  }
2596
2385
  .ps-tryon-sr-fit-input::-webkit-outer-spin-button,
2597
2386
  .ps-tryon-sr-fit-input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
2598
- .ps-tryon-sr-fit-input:focus { border-color: #bb945c; }
2387
+ .ps-tryon-sr-fit-input:focus { border-color: #bb945c; background: #1a1b1a; }
2388
+ .ps-tryon-sr-fit-unit { font-size: 0.68vw; color: #666; }
2389
+
2390
+ .ps-tryon-sr-fit-badge {
2391
+ display: inline-flex; align-items: center; gap: 0.26vw; padding: 0.26vw 0.57vw;
2392
+ border-radius: 0.36vw; font-size: 0.68vw; font-weight: 700; white-space: nowrap;
2393
+ }
2394
+ .ps-tryon-sr-fit-badge.ps-fit-good { background: rgba(74,222,128,0.1); color: #4ade80; }
2395
+ .ps-tryon-sr-fit-badge.ps-fit-tight { background: rgba(245,158,11,0.1); color: #f59e0b; }
2396
+ .ps-tryon-sr-fit-badge.ps-fit-loose { background: rgba(96,165,250,0.1); color: #60a5fa; }
2397
+
2398
+ /* Compare dropdown */
2399
+ .ps-tryon-sr-compare { }
2400
+ .ps-tryon-sr-compare-select {
2401
+ width: 100%; padding: 0.57vw 2vw 0.57vw 0.83vw; border: 1.5px solid #333; border-radius: 0.57vw;
2402
+ background: #1a1b1a; color: #fff; font-size: 0.83vw; font-weight: 600; font-family: inherit;
2403
+ appearance: none; -webkit-appearance: none; cursor: pointer; outline: none;
2404
+ 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");
2405
+ background-repeat: no-repeat; background-position: right 0.73vw center;
2406
+ }
2407
+ .ps-tryon-sr-compare-select:focus { border-color: #bb945c; }
2408
+ .ps-tryon-sr-compare-select option { background: #1a1b1a; color: #fff; }
2409
+ .ps-tryon-sr-compare-note {
2410
+ font-size: 0.73vw; color: #bb945c; margin-top: 0.42vw; display: flex; align-items: center; gap: 0.52vw;
2411
+ }
2412
+ .ps-tryon-sr-compare-reset {
2413
+ background: none; border: 1px solid #bb945c; color: #bb945c; padding: 0.16vw 0.52vw;
2414
+ border-radius: 0.31vw; font-size: 0.63vw; font-weight: 600; cursor: pointer;
2415
+ font-family: inherit; transition: all 0.2s;
2416
+ }
2417
+ .ps-tryon-sr-compare-reset:hover { background: rgba(187,148,92,0.1); }
2599
2418
 
2600
2419
  .ps-tryon-sr-fit-badge {
2601
2420
  display: inline-flex; align-items: center; gap: 0.26vw; padding: 0.21vw 0.52vw;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@primestyleai/tryon",
3
- "version": "3.17.0",
3
+ "version": "3.19.0",
4
4
  "description": "PrimeStyle Virtual Try-On SDK — React component & Web Component",
5
5
  "type": "module",
6
6
  "main": "dist/primestyle-tryon.js",