@primestyleai/tryon 2.0.1 → 2.0.2

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.
Files changed (2) hide show
  1. package/dist/react/index.js +92 -60
  2. package/package.json +1 -1
@@ -60,6 +60,22 @@ function lbsToKg(lbs) {
60
60
  function ftInToCm(ft, inch) {
61
61
  return +(ft * 30.48 + inch * 2.54).toFixed(1);
62
62
  }
63
+ function SvgIcon({ d, size = 18, strokeWidth = 2 }) {
64
+ return /* @__PURE__ */ jsx(
65
+ "svg",
66
+ {
67
+ width: size,
68
+ height: size,
69
+ viewBox: "0 0 24 24",
70
+ fill: "none",
71
+ stroke: "currentColor",
72
+ strokeWidth,
73
+ strokeLinecap: "round",
74
+ strokeLinejoin: "round",
75
+ children: /* @__PURE__ */ jsx("path", { d })
76
+ }
77
+ );
78
+ }
63
79
  function CameraIcon({ size = 18 }) {
64
80
  return /* @__PURE__ */ jsxs("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round", children: [
65
81
  /* @__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" }),
@@ -160,7 +176,9 @@ function PrimeStyleTryon({
160
176
  const [resultImageUrl, setResultImageUrl] = useState(null);
161
177
  const [errorMessage, setErrorMessage] = useState(null);
162
178
  const [dragOver, setDragOver] = useState(false);
163
- const [countdown, setCountdown] = useState(25);
179
+ const countdownRef = useRef(25);
180
+ const countdownElRef = useRef(null);
181
+ const countdownCircleRef = useRef(null);
164
182
  const [sizingMethod, setSizingMethod] = useState(null);
165
183
  const [sizingResult, setSizingResult] = useState(null);
166
184
  const [sizeGuide, setSizeGuide] = useState(null);
@@ -199,15 +217,12 @@ function PrimeStyleTryon({
199
217
  }, [apiUrl]);
200
218
  useEffect(() => {
201
219
  if (view === "processing") {
202
- setCountdown(25);
220
+ countdownRef.current = 25;
203
221
  const interval = setInterval(() => {
204
- setCountdown((p) => {
205
- if (p <= 1) {
206
- clearInterval(interval);
207
- return 0;
208
- }
209
- return p - 1;
210
- });
222
+ countdownRef.current -= 1;
223
+ if (countdownElRef.current) countdownElRef.current.textContent = String(Math.max(countdownRef.current, 0));
224
+ if (countdownCircleRef.current) countdownCircleRef.current.style.strokeDashoffset = `${Math.max(countdownRef.current, 0) / 25 * 326.73}`;
225
+ if (countdownRef.current <= 0) clearInterval(interval);
211
226
  }, 1e3);
212
227
  return () => clearInterval(interval);
213
228
  }
@@ -709,23 +724,33 @@ function PrimeStyleTryon({
709
724
  setView("sizing-form");
710
725
  }, children: [
711
726
  /* @__PURE__ */ jsx("div", { className: "ps-tryon-choice-icon", children: /* @__PURE__ */ jsx(RulerIcon, { size: 24 }) }),
712
- /* @__PURE__ */ jsx("div", { className: "ps-tryon-choice-title", children: "Exact Measurements" }),
713
- /* @__PURE__ */ jsx("div", { className: "ps-tryon-choice-desc", children: "Enter your body measurements for the most accurate fit" }),
714
- /* @__PURE__ */ jsx("span", { className: "ps-tryon-choice-badge", children: "Most Accurate" })
727
+ /* @__PURE__ */ jsxs("div", { className: "ps-tryon-choice-info", children: [
728
+ /* @__PURE__ */ jsx("div", { className: "ps-tryon-choice-title", children: "Enter my measurements" }),
729
+ /* @__PURE__ */ jsx("div", { className: "ps-tryon-choice-desc", children: "Chest, waist, hips, shoes & more" })
730
+ ] }),
731
+ /* @__PURE__ */ jsx("span", { className: "ps-tryon-choice-badge", children: "Best accuracy" })
715
732
  ] }),
716
733
  /* @__PURE__ */ jsxs("button", { className: "ps-tryon-choice-card", onClick: () => {
717
734
  setSizingMethod("quick");
718
735
  setView("sizing-form");
719
736
  }, children: [
720
737
  /* @__PURE__ */ jsx("div", { className: "ps-tryon-choice-icon", children: /* @__PURE__ */ jsx(SparkleIcon, { size: 24 }) }),
721
- /* @__PURE__ */ jsx("div", { className: "ps-tryon-choice-title", children: "Quick Estimate" }),
722
- /* @__PURE__ */ jsx("div", { className: "ps-tryon-choice-desc", children: "Just height, weight & gender — fast but less precise" })
738
+ /* @__PURE__ */ jsxs("div", { className: "ps-tryon-choice-info", children: [
739
+ /* @__PURE__ */ jsx("div", { className: "ps-tryon-choice-title", children: "Just height & weight" }),
740
+ /* @__PURE__ */ jsx("div", { className: "ps-tryon-choice-desc", children: "Quick estimate in seconds" })
741
+ ] })
742
+ ] }),
743
+ /* @__PURE__ */ jsxs("button", { className: "ps-tryon-choice-card", onClick: () => {
744
+ setSizingMethod(null);
745
+ handleSubmit();
746
+ }, children: [
747
+ /* @__PURE__ */ jsx("div", { className: "ps-tryon-choice-icon", children: /* @__PURE__ */ jsx(SvgIcon, { d: "M13 5H1M13 9H1M13 13H1M5 17l4 4 8-8", size: 24, strokeWidth: 1.5 }) }),
748
+ /* @__PURE__ */ jsxs("div", { className: "ps-tryon-choice-info", children: [
749
+ /* @__PURE__ */ jsx("div", { className: "ps-tryon-choice-title", children: "Skip sizing" }),
750
+ /* @__PURE__ */ jsx("div", { className: "ps-tryon-choice-desc", children: "Just show me the try-on" })
751
+ ] })
723
752
  ] })
724
- ] }),
725
- /* @__PURE__ */ jsx("button", { className: "ps-tryon-skip-btn", onClick: () => {
726
- setSizingMethod(null);
727
- handleSubmit();
728
- }, children: "Skip sizing — just try it on" })
753
+ ] })
729
754
  ] });
730
755
  }
731
756
  function SizingFormView() {
@@ -744,17 +769,19 @@ function PrimeStyleTryon({
744
769
  ] }),
745
770
  /* @__PURE__ */ jsxs("div", { className: "ps-tryon-input-row", children: [
746
771
  /* @__PURE__ */ jsx("label", { children: "Height" }),
747
- /* @__PURE__ */ jsx(UnitToggle, { options: [{ label: "cm", value: "cm" }, { label: "ft/in", value: "ft" }], value: heightUnit, onChange: setHeightUnit })
772
+ heightUnit === "ft" ? /* @__PURE__ */ jsxs("div", { className: "ps-tryon-height-ft", children: [
773
+ /* @__PURE__ */ jsx("input", { type: "number", placeholder: "5", value: formData.heightFeet || "", onChange: (e) => updateField("heightFeet", e.target.value) }),
774
+ /* @__PURE__ */ jsx("span", { children: "ft" }),
775
+ /* @__PURE__ */ jsx("input", { type: "number", placeholder: "4", value: formData.heightInches || "", onChange: (e) => updateField("heightInches", e.target.value) }),
776
+ /* @__PURE__ */ jsx("span", { children: "in" })
777
+ ] }) : /* @__PURE__ */ jsx("input", { type: "number", placeholder: "e.g. 175", value: formData.height || "", onChange: (e) => updateField("height", e.target.value) }),
778
+ /* @__PURE__ */ jsx(UnitToggle, { options: [{ label: "cm", value: "cm" }, { label: "ft", value: "ft" }], value: heightUnit, onChange: setHeightUnit })
748
779
  ] }),
749
- heightUnit === "ft" ? /* @__PURE__ */ jsxs("div", { className: "ps-tryon-input-row ps-tryon-dual-input", children: [
750
- /* @__PURE__ */ jsx("input", { type: "number", placeholder: "ft", value: formData.heightFeet || "", onChange: (e) => updateField("heightFeet", e.target.value) }),
751
- /* @__PURE__ */ jsx("input", { type: "number", placeholder: "in", value: formData.heightInches || "", onChange: (e) => updateField("heightInches", e.target.value) })
752
- ] }) : /* @__PURE__ */ jsx(InputRow, { label: "", fieldKey: "height", placeholder: "e.g. 175", type: "number", unit: "cm" }),
753
780
  /* @__PURE__ */ jsxs("div", { className: "ps-tryon-input-row", children: [
754
781
  /* @__PURE__ */ jsx("label", { children: "Weight" }),
782
+ /* @__PURE__ */ jsx("input", { type: "number", placeholder: weightUnit === "lbs" ? "e.g. 170" : "e.g. 75", value: formData.weight || "", onChange: (e) => updateField("weight", e.target.value) }),
755
783
  /* @__PURE__ */ jsx(UnitToggle, { options: [{ label: "kg", value: "kg" }, { label: "lbs", value: "lbs" }], value: weightUnit, onChange: setWeightUnit })
756
784
  ] }),
757
- /* @__PURE__ */ jsx(InputRow, { label: "", fieldKey: "weight", placeholder: weightUnit === "lbs" ? "e.g. 170" : "e.g. 75", type: "number", unit: weightUnit }),
758
785
  sizingMethod === "exact" && /* @__PURE__ */ jsxs(Fragment, { children: [
759
786
  /* @__PURE__ */ jsxs("div", { className: "ps-tryon-input-row", children: [
760
787
  /* @__PURE__ */ jsx("label", { children: "Measurements" }),
@@ -799,15 +826,15 @@ function PrimeStyleTryon({
799
826
  }
800
827
  function ProcessingView() {
801
828
  return /* @__PURE__ */ jsxs("div", { className: "ps-tryon-processing", children: [
802
- countdown > 0 ? /* @__PURE__ */ jsxs("div", { className: "ps-tryon-countdown-ring", children: [
829
+ /* @__PURE__ */ jsxs("div", { className: "ps-tryon-countdown-ring", children: [
803
830
  /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 120 120", children: [
804
831
  /* @__PURE__ */ jsx("circle", { className: "ps-tryon-countdown-track", cx: "60", cy: "60", r: "52" }),
805
- /* @__PURE__ */ jsx("circle", { className: "ps-tryon-countdown-progress", cx: "60", cy: "60", r: "52", style: { strokeDashoffset: `${countdown / 25 * 326.73}` } })
832
+ /* @__PURE__ */ jsx("circle", { ref: countdownCircleRef, className: "ps-tryon-countdown-progress", cx: "60", cy: "60", r: "52", style: { strokeDashoffset: `${25 / 25 * 326.73}` } })
806
833
  ] }),
807
- /* @__PURE__ */ jsx("span", { className: "ps-tryon-countdown-number", children: countdown })
808
- ] }) : /* @__PURE__ */ jsx("div", { className: "ps-tryon-done-pulse", children: /* @__PURE__ */ jsx("div", { className: cx("ps-tryon-spinner", cn.spinner) }) }),
809
- /* @__PURE__ */ jsx("p", { className: cx("ps-tryon-processing-text", cn.processingText), children: countdown > 0 ? "Generating your try-on..." : "Almost there..." }),
810
- /* @__PURE__ */ jsx("p", { className: cx("ps-tryon-processing-sub", cn.processingSubText), children: countdown > 0 ? "This usually takes 15-25 seconds" : "Finishing up your look" })
834
+ /* @__PURE__ */ jsx("span", { ref: countdownElRef, className: "ps-tryon-countdown-number", children: "25" })
835
+ ] }),
836
+ /* @__PURE__ */ jsx("p", { className: cx("ps-tryon-processing-text", cn.processingText), children: "Generating your try-on..." }),
837
+ /* @__PURE__ */ jsx("p", { className: cx("ps-tryon-processing-sub", cn.processingSubText), children: "This usually takes 15-25 seconds" })
811
838
  ] });
812
839
  }
813
840
  function ResultView() {
@@ -851,14 +878,17 @@ function PrimeStyleTryon({
851
878
  /* @__PURE__ */ jsx("button", { onClick: handleRetry, className: cx("ps-tryon-btn-retry", cn.retryButton), children: "Try Another" })
852
879
  ] }),
853
880
  sizingMethod && !profileSaved && /* @__PURE__ */ jsxs("div", { className: "ps-tryon-save-prompt", children: [
854
- /* @__PURE__ */ jsx("input", { type: "text", placeholder: "Profile name (e.g. Me)", value: profileName, onChange: (e) => setProfileName(e.target.value) }),
855
- /* @__PURE__ */ jsx("button", { onClick: () => {
856
- if (profileName.trim()) saveProfile(profileName.trim());
857
- }, children: "Save" })
881
+ /* @__PURE__ */ jsx("div", { className: "ps-tryon-save-label", children: "Save your measurements for next time" }),
882
+ /* @__PURE__ */ jsxs("div", { className: "ps-tryon-save-row", children: [
883
+ /* @__PURE__ */ jsx("input", { type: "text", placeholder: "Name this profile (e.g. John, Sarah)", value: profileName, onChange: (e) => setProfileName(e.target.value) }),
884
+ /* @__PURE__ */ jsx("button", { onClick: () => {
885
+ if (profileName.trim()) saveProfile(profileName.trim());
886
+ }, children: "Save" })
887
+ ] })
858
888
  ] }),
859
889
  profileSaved && /* @__PURE__ */ jsxs("div", { className: "ps-tryon-save-done", children: [
860
890
  /* @__PURE__ */ jsx(CheckIcon, {}),
861
- " Profile saved"
891
+ " Measurements saved to profile"
862
892
  ] })
863
893
  ] })
864
894
  ] });
@@ -1168,28 +1198,25 @@ const STYLES = `
1168
1198
 
1169
1199
  /* Sizing choice */
1170
1200
  .ps-tryon-sizing-choice { text-align: center; }
1171
- .ps-tryon-section-title { font-size: 16px; font-weight: 600; color: #fff; margin: 0 0 20px; }
1172
- .ps-tryon-choice-cards { display: flex; flex-direction: column; gap: 12px; }
1201
+ .ps-tryon-section-title { font-size: 16px; font-weight: 600; color: #fff; margin: 0 0 16px; }
1202
+ .ps-tryon-choice-cards { display: flex; flex-direction: column; gap: 10px; }
1173
1203
  .ps-tryon-choice-card {
1174
- padding: 20px; border: 1.5px solid #333; border-radius: 14px;
1204
+ display: flex; align-items: center; gap: 14px; padding: 16px;
1205
+ border: 1.5px solid #333; border-radius: 12px;
1175
1206
  background: #1a1b1a; cursor: pointer; transition: all 0.25s; text-align: left;
1176
- display: block; width: 100%; font-family: inherit;
1207
+ width: 100%; font-family: inherit; position: relative; overflow: hidden;
1177
1208
  }
1178
- .ps-tryon-choice-card:hover { border-color: #bb945c; box-shadow: 0 4px 20px rgba(187,148,92,0.08); }
1179
- .ps-tryon-choice-icon { color: #bb945c; margin-bottom: 8px; }
1209
+ .ps-tryon-choice-card:hover { border-color: #bb945c; transform: translateY(-1px); box-shadow: 0 6px 20px rgba(187,148,92,0.08); }
1210
+ .ps-tryon-choice-card:hover::before { content: ''; position: absolute; left: 0; top: 0; bottom: 0; width: 3px; background: #bb945c; }
1211
+ .ps-tryon-choice-icon { color: #bb945c; flex-shrink: 0; }
1180
1212
  .ps-tryon-choice-icon svg { stroke: currentColor; fill: none; }
1181
- .ps-tryon-choice-title { font-size: 15px; font-weight: 600; color: #fff; margin-bottom: 4px; }
1182
- .ps-tryon-choice-desc { font-size: 12px; color: #999; }
1213
+ .ps-tryon-choice-info { flex: 1; min-width: 0; }
1214
+ .ps-tryon-choice-title { font-size: 14px; font-weight: 600; color: #fff; }
1215
+ .ps-tryon-choice-desc { font-size: 12px; color: #999; margin-top: 2px; }
1183
1216
  .ps-tryon-choice-badge {
1184
- display: inline-block; margin-top: 8px; padding: 3px 10px; border-radius: 20px;
1185
- background: rgba(187,148,92,0.12); color: #bb945c; font-size: 11px; font-weight: 600;
1186
- }
1187
- .ps-tryon-skip-btn {
1188
- margin-top: 16px; padding: 10px; background: none; border: 1px solid #333;
1189
- border-radius: 10px; color: #999; font-size: 13px; cursor: pointer; transition: all 0.2s;
1190
- width: 100%; font-family: inherit;
1217
+ padding: 3px 10px; border-radius: 20px; flex-shrink: 0;
1218
+ background: rgba(187,148,92,0.12); color: #bb945c; font-size: 10px; font-weight: 600;
1191
1219
  }
1192
- .ps-tryon-skip-btn:hover { border-color: #666; color: #ccc; }
1193
1220
 
1194
1221
  /* Sizing form */
1195
1222
  .ps-tryon-sizing-form { display: flex; flex-direction: column; gap: 12px; }
@@ -1204,8 +1231,11 @@ const STYLES = `
1204
1231
  .ps-tryon-input-row input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
1205
1232
  .ps-tryon-input-row input:focus { border-color: #bb945c; }
1206
1233
  .ps-tryon-input-unit { font-size: 12px; color: #666; flex-shrink: 0; }
1207
- .ps-tryon-dual-input { gap: 8px; }
1208
- .ps-tryon-dual-input input { width: 50%; }
1234
+ .ps-tryon-height-ft { display: flex; align-items: center; gap: 6px; flex: 1; }
1235
+ .ps-tryon-height-ft input { width: 60px; padding: 10px 10px; border: 1.5px solid #333; border-radius: 10px; background: #1a1b1a; color: #fff; font-size: 14px; font-family: inherit; outline: none; text-align: center; -moz-appearance: textfield; }
1236
+ .ps-tryon-height-ft input::-webkit-outer-spin-button, .ps-tryon-height-ft input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
1237
+ .ps-tryon-height-ft input:focus { border-color: #bb945c; }
1238
+ .ps-tryon-height-ft span { font-size: 12px; color: #666; }
1209
1239
  .ps-tryon-country-select {
1210
1240
  flex: 1; padding: 10px 36px 10px 14px; border: 1.5px solid #333; border-radius: 10px;
1211
1241
  background: #1a1b1a; color: #fff; font-size: 13px; font-family: inherit;
@@ -1280,17 +1310,19 @@ const STYLES = `
1280
1310
  .ps-fit-good { color: #4ade80; } .ps-fit-tight { color: #f59e0b; } .ps-fit-loose { color: #60a5fa; }
1281
1311
 
1282
1312
  /* Save profile prompt */
1283
- .ps-tryon-save-prompt { display: flex; gap: 8px; margin-top: 14px; }
1284
- .ps-tryon-save-prompt input {
1313
+ .ps-tryon-save-prompt { margin-top: 14px; padding: 14px; border: 1px solid #333; border-radius: 12px; background: #1a1b1a; }
1314
+ .ps-tryon-save-label { font-size: 12px; color: #999; margin-bottom: 10px; }
1315
+ .ps-tryon-save-row { display: flex; gap: 8px; }
1316
+ .ps-tryon-save-row input {
1285
1317
  flex: 1; padding: 10px 14px; border: 1.5px solid #333; border-radius: 10px;
1286
- background: #1a1b1a; color: #fff; font-size: 13px; font-family: inherit; outline: none;
1318
+ background: #111211; color: #fff; font-size: 13px; font-family: inherit; outline: none;
1287
1319
  }
1288
- .ps-tryon-save-prompt input:focus { border-color: #bb945c; }
1289
- .ps-tryon-save-prompt button {
1320
+ .ps-tryon-save-row input:focus { border-color: #bb945c; }
1321
+ .ps-tryon-save-row button {
1290
1322
  padding: 10px 20px; background: #bb945c; color: #111; border: none; border-radius: 10px;
1291
1323
  font-size: 13px; font-weight: 600; cursor: pointer; transition: opacity 0.2s; font-family: inherit;
1292
1324
  }
1293
- .ps-tryon-save-prompt button:hover { opacity: 0.9; }
1325
+ .ps-tryon-save-row button:hover { opacity: 0.9; }
1294
1326
  .ps-tryon-save-done { font-size: 12px; color: #4ade80; margin-top: 10px; display: flex; align-items: center; gap: 6px; justify-content: center; }
1295
1327
  .ps-tryon-save-done svg { stroke: currentColor; }
1296
1328
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@primestyleai/tryon",
3
- "version": "2.0.1",
3
+ "version": "2.0.2",
4
4
  "description": "PrimeStyle Virtual Try-On SDK — React component & Web Component",
5
5
  "type": "module",
6
6
  "main": "dist/primestyle-tryon.js",