@primestyleai/tryon 3.18.0 → 3.20.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.
@@ -308,6 +308,10 @@ const en = {
308
308
  "Compare with another size": "Compare with another size",
309
309
  "recommended": "recommended",
310
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",
314
+ "Edit values": "Edit values",
311
315
  "Fit Analysis": "Fit Analysis",
312
316
  "Show more": "Show more",
313
317
  "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-Lf4qO_jW.js";
2
- import { P, b, T, d, r } from "./index-Lf4qO_jW.js";
1
+ import { c as createT, A as ApiClient, S as SseClient, i as isValidImageFile, a as compressImage } from "./index-Dr4MxT8W.js";
2
+ import { P, b, T, d, r } from "./index-Dr4MxT8W.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-Lf4qO_jW.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-Dr4MxT8W.js";
5
5
  const HEADER_ALIASES = {
6
6
  // ── Size label columns (skipped during field derivation) ──
7
7
  size: "__size__",
@@ -406,6 +406,26 @@ 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 SIZE_CONVERSIONS = {
410
+ "XXS": { US: "XXS", UK: "4", EU: "30", IT: "36", FR: "32", DE: "30", JP: "3", KR: "40", AU: "4", BR: "PP" },
411
+ "XS": { US: "XS", UK: "6", EU: "32", IT: "38", FR: "34", DE: "32", JP: "5", KR: "44", AU: "6", BR: "PP" },
412
+ "S": { US: "S", UK: "8", EU: "36", IT: "42", FR: "38", DE: "36", JP: "9", KR: "55", AU: "10", BR: "P" },
413
+ "M": { US: "M", UK: "10", EU: "38", IT: "44", FR: "40", DE: "38", JP: "11", KR: "66", AU: "12", BR: "M" },
414
+ "L": { US: "L", UK: "12", EU: "40", IT: "46", FR: "42", DE: "40", JP: "13", KR: "77", AU: "14", BR: "G" },
415
+ "XL": { US: "XL", UK: "14", EU: "42", IT: "48", FR: "44", DE: "42", JP: "15", KR: "88", AU: "16", BR: "GG" },
416
+ "XXL": { US: "XXL", UK: "16", EU: "44", IT: "50", FR: "46", DE: "44", JP: "17", KR: "99", AU: "18", BR: "XG" },
417
+ "XXXL": { US: "XXXL", UK: "18", EU: "46", IT: "52", FR: "48", DE: "46", JP: "19", KR: "100", AU: "20", BR: "EG" },
418
+ // Numeric sizes (EU-based)
419
+ "34": { US: "XS", UK: "6", EU: "34", IT: "38", FR: "34", DE: "34", JP: "5", KR: "44", AU: "6" },
420
+ "36": { US: "S", UK: "8", EU: "36", IT: "40", FR: "36", DE: "36", JP: "7", KR: "55", AU: "8" },
421
+ "38": { US: "M", UK: "10", EU: "38", IT: "42", FR: "38", DE: "38", JP: "9", KR: "66", AU: "10" },
422
+ "40": { US: "L", UK: "12", EU: "40", IT: "44", FR: "40", DE: "40", JP: "11", KR: "77", AU: "12" },
423
+ "42": { US: "XL", UK: "14", EU: "42", IT: "46", FR: "42", DE: "42", JP: "13", KR: "88", AU: "14" },
424
+ "44": { US: "XXL", UK: "16", EU: "44", IT: "48", FR: "44", DE: "44", JP: "15", KR: "99", AU: "16" },
425
+ "46": { US: "XXXL", UK: "18", EU: "46", IT: "50", FR: "46", DE: "46", JP: "17", KR: "100", AU: "18" },
426
+ "48": { US: "XXXL", UK: "20", EU: "48", IT: "52", FR: "48", DE: "48", JP: "19", KR: "105", AU: "20" },
427
+ "50": { US: "XXXL", UK: "22", EU: "50", IT: "54", FR: "50", DE: "50", JP: "21", KR: "110", AU: "22" }
428
+ };
409
429
  const STEP_LABELS = ["", "Welcome", "Size", "Your Fit", "Try On"];
410
430
  const TOTAL_STEPS = 4;
411
431
  function detectLocale() {
@@ -424,9 +444,6 @@ function detectLocale() {
424
444
  function isImperial(locale) {
425
445
  return locale === "US" || locale === "UK";
426
446
  }
427
- function cmToIn(cm) {
428
- return +(cm / 2.54).toFixed(1);
429
- }
430
447
  function inToCm(inches) {
431
448
  return +(inches * 2.54).toFixed(1);
432
449
  }
@@ -436,6 +453,22 @@ function lbsToKg(lbs) {
436
453
  function ftInToCm(ft, inch) {
437
454
  return +(ft * 30.48 + inch * 2.54).toFixed(1);
438
455
  }
456
+ function SvgIcon({ d, size = 18, strokeWidth = 2 }) {
457
+ return /* @__PURE__ */ jsx(
458
+ "svg",
459
+ {
460
+ width: size,
461
+ height: size,
462
+ viewBox: "0 0 24 24",
463
+ fill: "none",
464
+ stroke: "currentColor",
465
+ strokeWidth,
466
+ strokeLinecap: "round",
467
+ strokeLinejoin: "round",
468
+ children: /* @__PURE__ */ jsx("path", { d })
469
+ }
470
+ );
471
+ }
439
472
  function CameraIcon({ size = 18 }) {
440
473
  return /* @__PURE__ */ jsxs("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round", children: [
441
474
  /* @__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" }),
@@ -826,115 +859,7 @@ function PrimeStyleTryonInner({
826
859
  }
827
860
  return formGender === "female" ? FALLBACK_FIELDS_FEMALE : FALLBACK_FIELDS_MALE;
828
861
  }, [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
862
  const submitSizing = useCallback(async () => {
932
- const localResult = computeSizingLocally();
933
- if (localResult) {
934
- setSizingResult(localResult);
935
- setSizingLoading(false);
936
- return;
937
- }
938
863
  if (!apiRef.current) return;
939
864
  const baseUrl = getApiUrl(apiUrl);
940
865
  const key = getApiKey();
@@ -993,7 +918,7 @@ function PrimeStyleTryonInner({
993
918
  } finally {
994
919
  setSizingLoading(false);
995
920
  }
996
- }, [apiUrl, sizingMethod, sizingCountry, heightUnit, weightUnit, sizingUnit, sizeGuide, productTitle, dynamicFields, computeSizingLocally]);
921
+ }, [apiUrl, sizingMethod, sizingCountry, heightUnit, weightUnit, sizingUnit, sizeGuide, productTitle, dynamicFields]);
997
922
  const handleTryOnSubmit = useCallback(async () => {
998
923
  if (!selectedFile || !apiRef.current || !sseRef.current) {
999
924
  const msg = !apiRef.current ? t("SDK not configured. Please provide an API key.") : t("Something went wrong");
@@ -1582,65 +1507,71 @@ function PrimeStyleTryonInner({
1582
1507
  ] }, `form-${formGender}-${sizingUnit}-${heightUnit}-${sizingCountry}-${formKey}`);
1583
1508
  }
1584
1509
  function SizeResultView() {
1585
- const isCmR = sizingUnit === "cm";
1586
- const unitLbl = isCmR ? t("cm") : t("in");
1587
- const allSizes = useMemo(() => {
1588
- if (!sizeGuide?.found || !sizeGuide.headers || !sizeGuide.rows) return [];
1589
- const idx = sizeGuide.headers.findIndex((h) => /^size$/i.test(h.trim()));
1590
- const col = idx >= 0 ? idx : 0;
1591
- return sizeGuide.rows.map((r) => r[col]?.trim()).filter(Boolean);
1592
- }, [sizeGuide]);
1593
- const [compareSize, setCompareSize] = useState("");
1510
+ const unitLbl = sizingUnit === "cm" ? t("cm") : t("in");
1511
+ const [editing, setEditing] = useState(false);
1594
1512
  const [editVals, setEditVals] = useState({});
1595
- const parseNumFromDetail = (s) => {
1513
+ const [compareSize, setCompareSize] = useState("");
1514
+ const recSize = sizingResult?.recommendedSize || "";
1515
+ const selSize = compareSize || recSize;
1516
+ const sizeColIdx = useMemo(() => {
1517
+ if (!sizeGuide?.headers || !sizeGuide?.rows) return -1;
1518
+ const byName = sizeGuide.headers.findIndex((h) => /size|taglia|größe|taille/i.test(h.trim()));
1519
+ if (byName >= 0) return byName;
1520
+ for (let c = 0; c < sizeGuide.headers.length; c++) {
1521
+ const vals = sizeGuide.rows.map((r) => r[c]?.trim() || "");
1522
+ if (vals.some((v) => /^(XXS|XS|S|M|L|XL|XXL|XXXL|ONE SIZE|\d{1,2})$/i.test(v))) return c;
1523
+ }
1524
+ return 0;
1525
+ }, [sizeGuide]);
1526
+ const allSizes = useMemo(() => {
1527
+ if (sizeColIdx < 0 || !sizeGuide?.rows) return [];
1528
+ return sizeGuide.rows.map((r) => r[sizeColIdx]?.trim()).filter(Boolean);
1529
+ }, [sizeGuide, sizeColIdx]);
1530
+ const pNum = (s) => {
1596
1531
  const n = parseFloat(s.replace(/[^\d.]/g, ""));
1597
1532
  return isNaN(n) ? 0 : n;
1598
1533
  };
1599
- const parseRangeFromDetail = (s) => {
1600
- const nums = s.replace(/[^\d.\-–]/g, " ").trim().split(/[\s\-–]+/).filter(Boolean).map(Number).filter((n) => !isNaN(n));
1601
- if (nums.length === 0) return { min: 0, max: 0 };
1602
- return { min: Math.min(...nums), max: Math.max(...nums) };
1534
+ const pRange = (s) => {
1535
+ const ns = s.replace(/[^\d.\-–]/g, " ").trim().split(/[\s\-–]+/).filter(Boolean).map(Number).filter((n) => !isNaN(n));
1536
+ return ns.length ? { min: Math.min(...ns), max: Math.max(...ns) } : { min: 0, max: 0 };
1603
1537
  };
1604
- const getChartRangeForSize = useCallback((measurement, size) => {
1605
- if (!sizeGuide?.found || !sizeGuide.headers || !sizeGuide.rows) return null;
1606
- const sizeCol = sizeGuide.headers.findIndex((h) => /^size$/i.test(h.trim()));
1607
- const sc = sizeCol >= 0 ? sizeCol : 0;
1608
- const measCol = sizeGuide.headers.findIndex((h) => {
1609
- const a = h.toLowerCase().trim();
1610
- const b = measurement.toLowerCase().trim();
1538
+ const chartRangeFor = useCallback((measurement, size) => {
1539
+ if (!sizeGuide?.headers || !sizeGuide?.rows || sizeColIdx < 0) return null;
1540
+ const mc = sizeGuide.headers.findIndex((h) => {
1541
+ const a = h.toLowerCase().trim(), b = measurement.toLowerCase().trim();
1611
1542
  return a === b || a.includes(b) || b.includes(a);
1612
1543
  });
1613
- if (measCol < 0) return null;
1614
- const row = sizeGuide.rows.find((r) => r[sc]?.trim() === size);
1615
- if (!row || !row[measCol]) return null;
1616
- const cell = row[measCol];
1617
- const parsed = parseRangeFromDetail(cell);
1618
- return { range: cell, ...parsed };
1619
- }, [sizeGuide]);
1544
+ if (mc < 0) return null;
1545
+ const row = sizeGuide.rows.find((r) => r[sizeColIdx]?.trim() === size);
1546
+ return row?.[mc] ? { range: row[mc], ...pRange(row[mc]) } : null;
1547
+ }, [sizeGuide, sizeColIdx]);
1620
1548
  const fitRows = useMemo(() => {
1621
- const base = sizingResult?.matchDetails;
1622
- if (!base || base.length === 0) return [];
1623
- compareSize || sizingResult?.recommendedSize || "";
1624
- return base.map((m) => {
1625
- const editedRaw = editVals[m.measurement];
1626
- const origNum = parseNumFromDetail(m.userValue);
1627
- const userNum = editedRaw !== void 0 && editedRaw !== "" ? parseFloat(editedRaw) : origNum;
1628
- let chartRange = m.chartRange;
1629
- let rangeMin = parseRangeFromDetail(m.chartRange).min;
1630
- let rangeMax = parseRangeFromDetail(m.chartRange).max;
1631
- if (compareSize && compareSize !== sizingResult?.recommendedSize) {
1632
- const alt = getChartRangeForSize(m.measurement, compareSize);
1549
+ if (!sizingResult?.matchDetails?.length) return [];
1550
+ return sizingResult.matchDetails.map((m) => {
1551
+ const origNum = pNum(m.userValue);
1552
+ const edited = editVals[m.measurement];
1553
+ const userNum = edited !== void 0 && edited !== "" ? parseFloat(edited) : origNum;
1554
+ let { min: rMin, max: rMax } = pRange(m.chartRange);
1555
+ let chartLabel = m.chartRange;
1556
+ if (compareSize && compareSize !== sizingResult.recommendedSize) {
1557
+ const alt = chartRangeFor(m.measurement, compareSize);
1633
1558
  if (alt) {
1634
- chartRange = alt.range;
1635
- rangeMin = alt.min;
1636
- rangeMax = alt.max;
1559
+ chartLabel = alt.range;
1560
+ rMin = alt.min;
1561
+ rMax = alt.max;
1637
1562
  }
1638
1563
  }
1639
- const fit = userNum >= rangeMin && userNum <= rangeMax ? "good" : userNum < rangeMin ? "tight" : "loose";
1640
- return { measurement: m.measurement, userNum, userDisplay: `${userNum}`, chartRange, fit };
1564
+ const fit = userNum >= rMin && userNum <= rMax ? "good" : userNum < rMin ? "tight" : "loose";
1565
+ return { area: m.measurement, userNum, chartLabel, fit };
1641
1566
  });
1642
- }, [sizingResult, compareSize, editVals, getChartRangeForSize]);
1643
- const intlSizes = sizingResult?.internationalSizes || {};
1567
+ }, [sizingResult, compareSize, editVals, chartRangeFor]);
1568
+ const allIntlSizes = useMemo(() => {
1569
+ const backendIntl = sizingResult?.internationalSizes || {};
1570
+ const upper = recSize.toUpperCase().trim();
1571
+ const fromTable = SIZE_CONVERSIONS[upper] || SIZE_CONVERSIONS[recSize] || {};
1572
+ const merged = { ...fromTable, ...backendIntl };
1573
+ return merged;
1574
+ }, [sizingResult, recSize]);
1644
1575
  return /* @__PURE__ */ jsxs("div", { className: "ps-tryon-sr", children: [
1645
1576
  sizingLoading && !sizingResult && /* @__PURE__ */ jsxs("div", { className: "ps-tryon-sr-loading", children: [
1646
1577
  /* @__PURE__ */ jsx("div", { className: "ps-tryon-size-loading-spinner" }),
@@ -1648,69 +1579,80 @@ function PrimeStyleTryonInner({
1648
1579
  ] }),
1649
1580
  sizingResult && /* @__PURE__ */ jsxs(Fragment, { children: [
1650
1581
  /* @__PURE__ */ jsxs("div", { className: "ps-tryon-sr-hero", children: [
1651
- /* @__PURE__ */ jsx("div", { className: "ps-tryon-sr-hero-badge", children: sizingResult.recommendedSize }),
1582
+ recSize && /* @__PURE__ */ jsx("div", { className: "ps-tryon-sr-hero-badge", children: recSize }),
1652
1583
  /* @__PURE__ */ jsxs("div", { className: "ps-tryon-sr-hero-info", children: [
1653
1584
  /* @__PURE__ */ jsx("div", { className: "ps-tryon-sr-hero-title", children: t("Your Size") }),
1654
1585
  /* @__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") })
1655
1586
  ] })
1656
1587
  ] }),
1657
- Object.keys(intlSizes).length > 0 && /* @__PURE__ */ jsxs("div", { className: "ps-tryon-sr-intl", children: [
1658
- /* @__PURE__ */ jsx("div", { className: "ps-tryon-sr-label", children: t("Equivalent Sizes") }),
1659
- /* @__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: [
1588
+ Object.keys(allIntlSizes).length > 0 && /* @__PURE__ */ jsxs("div", { className: "ps-tryon-sr-intl", children: [
1589
+ /* @__PURE__ */ jsx("div", { className: "ps-tryon-sr-label", children: t("Your size in other countries") }),
1590
+ /* @__PURE__ */ jsx("div", { className: "ps-tryon-sr-intl-primary", children: Object.entries(allIntlSizes).map(([code, val]) => /* @__PURE__ */ jsxs("div", { className: "ps-tryon-sr-intl-card", children: [
1660
1591
  /* @__PURE__ */ jsx("span", { className: "ps-tryon-sr-intl-card-val", children: val }),
1661
1592
  /* @__PURE__ */ jsx("span", { className: "ps-tryon-sr-intl-card-code", children: code })
1662
1593
  ] }, code)) })
1663
1594
  ] }),
1664
1595
  allSizes.length > 1 && /* @__PURE__ */ jsxs("div", { className: "ps-tryon-sr-compare", children: [
1665
1596
  /* @__PURE__ */ jsx("div", { className: "ps-tryon-sr-label", children: t("Compare with another size") }),
1666
- /* @__PURE__ */ jsx(
1667
- "select",
1668
- {
1669
- className: "ps-tryon-sr-compare-select",
1670
- value: compareSize || sizingResult.recommendedSize,
1671
- onChange: (e) => setCompareSize(e.target.value),
1672
- children: allSizes.map((s) => /* @__PURE__ */ jsxs("option", { value: s, children: [
1673
- s,
1674
- s === sizingResult.recommendedSize ? ` ${t("recommended")}` : ""
1675
- ] }, s))
1676
- }
1677
- )
1597
+ /* @__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: [
1598
+ s,
1599
+ s === recSize ? ` ★ ${t("recommended")}` : ""
1600
+ ] }, s)) }),
1601
+ compareSize && compareSize !== recSize && /* @__PURE__ */ jsxs("div", { className: "ps-tryon-sr-compare-note", children: [
1602
+ t("Showing fit for size"),
1603
+ " ",
1604
+ /* @__PURE__ */ jsx("strong", { children: compareSize }),
1605
+ /* @__PURE__ */ jsxs("button", { className: "ps-tryon-sr-compare-reset", onClick: () => setCompareSize(""), children: [
1606
+ t("Back to"),
1607
+ " ",
1608
+ recSize
1609
+ ] })
1610
+ ] })
1678
1611
  ] }),
1679
1612
  fitRows.length > 0 && /* @__PURE__ */ jsxs("div", { className: "ps-tryon-sr-fit", children: [
1680
- /* @__PURE__ */ jsxs("div", { className: "ps-tryon-sr-label", children: [
1681
- t("Fit Analysis"),
1682
- compareSize && compareSize !== sizingResult.recommendedSize && /* @__PURE__ */ jsxs("span", { className: "ps-tryon-sr-label-hint", children: [
1683
- " ",
1684
- t("size"),
1685
- " ",
1686
- compareSize
1613
+ /* @__PURE__ */ jsxs("div", { className: "ps-tryon-sr-fit-header-row", children: [
1614
+ /* @__PURE__ */ jsx("span", { className: "ps-tryon-sr-label", children: t("Fit Analysis") }),
1615
+ /* @__PURE__ */ jsxs("button", { className: "ps-tryon-sr-edit-btn", onClick: () => setEditing(!editing), children: [
1616
+ /* @__PURE__ */ jsx(SvgIcon, { d: "M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7", size: 12 }),
1617
+ editing ? t("Done") : t("Edit values")
1687
1618
  ] })
1688
1619
  ] }),
1689
- /* @__PURE__ */ jsxs("div", { className: "ps-tryon-sr-fit-table", children: [
1690
- /* @__PURE__ */ jsxs("div", { className: "ps-tryon-sr-fit-header", children: [
1691
- /* @__PURE__ */ jsx("span", { className: "ps-tryon-sr-fit-col-area", children: t("Area") }),
1692
- /* @__PURE__ */ jsx("span", { className: "ps-tryon-sr-fit-col-you", children: t("You") }),
1693
- /* @__PURE__ */ jsx("span", { className: "ps-tryon-sr-fit-col-chart", children: t("Chart") }),
1694
- /* @__PURE__ */ jsx("span", { className: "ps-tryon-sr-fit-col-fit", children: t("Fit") })
1620
+ fitRows.map((row, i) => /* @__PURE__ */ jsxs("div", { className: `ps-tryon-sr-fit-card ps-fit-${row.fit}`, children: [
1621
+ /* @__PURE__ */ jsxs("div", { className: "ps-tryon-sr-fit-card-top", children: [
1622
+ /* @__PURE__ */ jsx("span", { className: "ps-tryon-sr-fit-area", children: row.area }),
1623
+ /* @__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")}` })
1695
1624
  ] }),
1696
- fitRows.map((row, i) => /* @__PURE__ */ jsxs("div", { className: `ps-tryon-sr-fit-row ps-fit-${row.fit}`, children: [
1697
- /* @__PURE__ */ jsx("span", { className: "ps-tryon-sr-fit-col-area", children: row.measurement }),
1698
- /* @__PURE__ */ jsxs("span", { className: "ps-tryon-sr-fit-col-you", children: [
1699
- /* @__PURE__ */ jsx(
1700
- "input",
1701
- {
1702
- type: "number",
1703
- className: "ps-tryon-sr-fit-input",
1704
- value: editVals[row.measurement] !== void 0 ? editVals[row.measurement] : row.userNum,
1705
- onChange: (e) => setEditVals((prev) => ({ ...prev, [row.measurement]: e.target.value }))
1706
- }
1707
- ),
1708
- /* @__PURE__ */ jsx("span", { className: "ps-tryon-sr-fit-unit", children: unitLbl })
1625
+ /* @__PURE__ */ jsxs("div", { className: "ps-tryon-sr-fit-card-bottom", children: [
1626
+ /* @__PURE__ */ jsxs("div", { className: "ps-tryon-sr-fit-val", children: [
1627
+ /* @__PURE__ */ jsx("span", { className: "ps-tryon-sr-fit-val-label", children: t("You") }),
1628
+ editing ? /* @__PURE__ */ jsxs("div", { className: "ps-tryon-sr-fit-val-input-wrap", children: [
1629
+ /* @__PURE__ */ jsx(
1630
+ "input",
1631
+ {
1632
+ type: "number",
1633
+ className: "ps-tryon-sr-fit-input",
1634
+ value: editVals[row.area] !== void 0 ? editVals[row.area] : row.userNum,
1635
+ onChange: (e) => setEditVals((prev) => ({ ...prev, [row.area]: e.target.value }))
1636
+ }
1637
+ ),
1638
+ /* @__PURE__ */ jsx("span", { className: "ps-tryon-sr-fit-unit", children: unitLbl })
1639
+ ] }) : /* @__PURE__ */ jsxs("span", { className: "ps-tryon-sr-fit-val-text", children: [
1640
+ row.userNum,
1641
+ " ",
1642
+ unitLbl
1643
+ ] })
1709
1644
  ] }),
1710
- /* @__PURE__ */ jsx("span", { className: "ps-tryon-sr-fit-col-chart", children: row.chartRange }),
1711
- /* @__PURE__ */ jsx("span", { className: "ps-tryon-sr-fit-col-fit", children: /* @__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") }) })
1712
- ] }, i))
1713
- ] })
1645
+ /* @__PURE__ */ jsxs("div", { className: "ps-tryon-sr-fit-val", children: [
1646
+ /* @__PURE__ */ jsxs("span", { className: "ps-tryon-sr-fit-val-label", children: [
1647
+ t("Chart"),
1648
+ " (",
1649
+ selSize,
1650
+ ")"
1651
+ ] }),
1652
+ /* @__PURE__ */ jsx("span", { className: "ps-tryon-sr-fit-val-text", children: row.chartLabel })
1653
+ ] })
1654
+ ] })
1655
+ ] }, i))
1714
1656
  ] }),
1715
1657
  fitRows.length === 0 && sizingResult.reasoning && /* @__PURE__ */ jsx("div", { className: "ps-tryon-sr-reasoning", children: /* @__PURE__ */ jsx("p", { children: sizingResult.reasoning }) }),
1716
1658
  /* @__PURE__ */ jsxs("div", { className: "ps-tryon-sr-ctas", children: [
@@ -1995,7 +1937,7 @@ const STYLES = `
1995
1937
  .ps-tryon-modal {
1996
1938
  background: var(--ps-modal-bg, #111211); color: var(--ps-modal-color, #fff);
1997
1939
  border-radius: var(--ps-modal-radius, 0.83vw); width: var(--ps-modal-width, 100%);
1998
- max-width: var(--ps-modal-max-width, 27vw); max-height: 92vh; overflow-y: auto;
1940
+ max-width: var(--ps-modal-max-width, 32vw); max-height: 92vh; overflow-y: auto;
1999
1941
  font-family: var(--ps-modal-font, system-ui, -apple-system, sans-serif);
2000
1942
  box-shadow: 0 1.3vw 2.6vw rgba(0,0,0,0.4); animation: ps-slide-up 0.3s ease;
2001
1943
  scrollbar-width: thin; scrollbar-color: #333 transparent;
@@ -2463,44 +2405,60 @@ const STYLES = `
2463
2405
  .ps-tryon-sr-comparing { font-size: 0.73vw; color: #bb945c; margin-top: 0.42vw; }
2464
2406
  .ps-tryon-sr-comparing strong { color: #d6ba7d; }
2465
2407
 
2466
- /* Fit analysis table */
2467
- .ps-tryon-sr-fit { }
2468
- .ps-tryon-sr-fit-table { border: 1.5px solid #333; border-radius: 0.73vw; overflow: hidden; }
2469
- .ps-tryon-sr-fit-header {
2470
- display: flex; padding: 0.57vw 0.83vw; background: #1a1b1a; border-bottom: 1px solid #333;
2471
- }
2472
- .ps-tryon-sr-fit-header span {
2473
- font-size: 0.68vw; font-weight: 700; color: #666; text-transform: uppercase; letter-spacing: 0.06em;
2474
- }
2475
- .ps-tryon-sr-fit-row {
2476
- display: flex; align-items: center; padding: 0.57vw 0.83vw;
2477
- border-bottom: 1px solid #222; transition: background 0.15s;
2478
- }
2479
- .ps-tryon-sr-fit-row:last-child { border-bottom: none; }
2480
- .ps-tryon-sr-fit-row:hover { background: rgba(255,255,255,0.02); }
2481
- .ps-tryon-sr-fit-row.ps-fit-good { border-left: 3px solid #4ade80; }
2482
- .ps-tryon-sr-fit-row.ps-fit-tight { border-left: 3px solid #f59e0b; }
2483
- .ps-tryon-sr-fit-row.ps-fit-loose { border-left: 3px solid #60a5fa; }
2484
-
2485
- .ps-tryon-sr-fit-col-area { flex: 0 0 28%; font-size: 0.78vw; font-weight: 600; color: #fff; }
2486
- .ps-tryon-sr-fit-col-you { flex: 0 0 22%; }
2487
- .ps-tryon-sr-fit-col-chart { flex: 0 0 22%; font-size: 0.78vw; color: #999; font-weight: 500; }
2488
- .ps-tryon-sr-fit-col-fit { flex: 1; text-align: right; }
2408
+ /* Fit analysis cards */
2409
+ .ps-tryon-sr-fit { display: flex; flex-direction: column; gap: 0.52vw; }
2410
+ .ps-tryon-sr-fit-header-row {
2411
+ display: flex; align-items: center; justify-content: space-between; margin-bottom: 0.1vw;
2412
+ }
2413
+ .ps-tryon-sr-edit-btn {
2414
+ display: inline-flex; align-items: center; gap: 0.31vw;
2415
+ padding: 0.26vw 0.63vw; border: 1.5px solid #bb945c; border-radius: 0.36vw;
2416
+ background: transparent; color: #bb945c; font-size: 0.63vw; font-weight: 600;
2417
+ cursor: pointer; font-family: inherit; transition: all 0.2s;
2418
+ }
2419
+ .ps-tryon-sr-edit-btn:hover { background: rgba(187,148,92,0.1); }
2420
+ .ps-tryon-sr-edit-btn svg { stroke: currentColor; }
2421
+ .ps-tryon-sr-fit-card {
2422
+ border: 1.5px solid #333; border-radius: 0.63vw; overflow: hidden; transition: border-color 0.2s;
2423
+ }
2424
+ .ps-tryon-sr-fit-card.ps-fit-good { border-left: 3px solid #4ade80; }
2425
+ .ps-tryon-sr-fit-card.ps-fit-tight { border-left: 3px solid #f59e0b; }
2426
+ .ps-tryon-sr-fit-card.ps-fit-loose { border-left: 3px solid #60a5fa; }
2427
+ .ps-tryon-sr-fit-card-top {
2428
+ display: flex; align-items: center; justify-content: space-between;
2429
+ padding: 0.52vw 0.83vw; background: #1a1b1a;
2430
+ }
2431
+ .ps-tryon-sr-fit-area { font-size: 0.83vw; font-weight: 600; color: #fff; }
2432
+ .ps-tryon-sr-fit-card-bottom {
2433
+ display: flex; gap: 1vw; padding: 0.52vw 0.83vw;
2434
+ }
2435
+ .ps-tryon-sr-fit-val { flex: 1; }
2436
+ .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; }
2437
+ .ps-tryon-sr-fit-val-input-wrap { display: flex; align-items: center; gap: 0.26vw; }
2438
+ .ps-tryon-sr-fit-val-text { font-size: 0.83vw; font-weight: 600; color: #ccc; }
2489
2439
 
2490
2440
  .ps-tryon-sr-fit-input {
2491
- width: 3.5vw; padding: 0.26vw 0.42vw; border: 1.5px solid #333; border-radius: 0.36vw;
2492
- background: #111211; color: #fff; font-size: 0.78vw; font-weight: 600; font-family: inherit;
2441
+ width: 4vw; padding: 0.31vw 0.42vw; border: 1.5px solid #444; border-radius: 0.36vw;
2442
+ background: #0c0c0d; color: #fff; font-size: 0.83vw; font-weight: 600; font-family: inherit;
2493
2443
  outline: none; text-align: center; -moz-appearance: textfield;
2494
2444
  }
2495
2445
  .ps-tryon-sr-fit-input::-webkit-outer-spin-button,
2496
2446
  .ps-tryon-sr-fit-input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
2497
- .ps-tryon-sr-fit-input:focus { border-color: #bb945c; }
2498
- .ps-tryon-sr-fit-unit { font-size: 0.57vw; color: #666; margin-left: 0.2vw; }
2447
+ .ps-tryon-sr-fit-input:focus { border-color: #bb945c; background: #1a1b1a; }
2448
+ .ps-tryon-sr-fit-unit { font-size: 0.68vw; color: #666; }
2449
+
2450
+ .ps-tryon-sr-fit-badge {
2451
+ display: inline-flex; align-items: center; gap: 0.26vw; padding: 0.26vw 0.57vw;
2452
+ border-radius: 0.36vw; font-size: 0.68vw; font-weight: 700; white-space: nowrap;
2453
+ }
2454
+ .ps-tryon-sr-fit-badge.ps-fit-good { background: rgba(74,222,128,0.1); color: #4ade80; }
2455
+ .ps-tryon-sr-fit-badge.ps-fit-tight { background: rgba(245,158,11,0.1); color: #f59e0b; }
2456
+ .ps-tryon-sr-fit-badge.ps-fit-loose { background: rgba(96,165,250,0.1); color: #60a5fa; }
2499
2457
 
2500
2458
  /* Compare dropdown */
2501
2459
  .ps-tryon-sr-compare { }
2502
2460
  .ps-tryon-sr-compare-select {
2503
- width: 100%; padding: 0.52vw 2vw 0.52vw 0.73vw; border: 1.5px solid #333; border-radius: 0.52vw;
2461
+ width: 100%; padding: 0.57vw 2vw 0.57vw 0.83vw; border: 1.5px solid #333; border-radius: 0.57vw;
2504
2462
  background: #1a1b1a; color: #fff; font-size: 0.83vw; font-weight: 600; font-family: inherit;
2505
2463
  appearance: none; -webkit-appearance: none; cursor: pointer; outline: none;
2506
2464
  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");
@@ -2508,6 +2466,15 @@ const STYLES = `
2508
2466
  }
2509
2467
  .ps-tryon-sr-compare-select:focus { border-color: #bb945c; }
2510
2468
  .ps-tryon-sr-compare-select option { background: #1a1b1a; color: #fff; }
2469
+ .ps-tryon-sr-compare-note {
2470
+ font-size: 0.73vw; color: #bb945c; margin-top: 0.42vw; display: flex; align-items: center; gap: 0.52vw;
2471
+ }
2472
+ .ps-tryon-sr-compare-reset {
2473
+ background: none; border: 1px solid #bb945c; color: #bb945c; padding: 0.16vw 0.52vw;
2474
+ border-radius: 0.31vw; font-size: 0.63vw; font-weight: 600; cursor: pointer;
2475
+ font-family: inherit; transition: all 0.2s;
2476
+ }
2477
+ .ps-tryon-sr-compare-reset:hover { background: rgba(187,148,92,0.1); }
2511
2478
 
2512
2479
  .ps-tryon-sr-fit-badge {
2513
2480
  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.18.0",
3
+ "version": "3.20.0",
4
4
  "description": "PrimeStyle Virtual Try-On SDK — React component & Web Component",
5
5
  "type": "module",
6
6
  "main": "dist/primestyle-tryon.js",