shopify-chatbot-widget 1.0.8 → 1.0.10

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 (46) hide show
  1. package/.env.development +2 -0
  2. package/dist/jaweb-chatbot.css +1 -1
  3. package/dist/jaweb-chatbot.iife.js +192 -599
  4. package/home.html +1 -1
  5. package/jawebDirect.js +1 -1
  6. package/package.json +1 -1
  7. package/src/App.tsx +9 -0
  8. package/src/Chatbot/Chatbot.css +193 -113
  9. package/src/Chatbot/Chatbot.tsx +26 -19
  10. package/src/Chatbot/components/Cart/Index.tsx +10 -8
  11. package/src/Chatbot/components/ChatInput.tsx +11 -11
  12. package/src/Chatbot/components/Home/Home.css +456 -0
  13. package/src/Chatbot/components/Home/Home.tsx +293 -0
  14. package/src/Chatbot/components/Messages/Addcart.tsx +1 -1
  15. package/src/Chatbot/components/Messages/AssistantMesage.tsx +78 -37
  16. package/src/Chatbot/components/Messages/CardSwiper.tsx +1 -1
  17. package/src/Chatbot/components/Messages/Carousel/CardSwiper.tsx +87 -92
  18. package/src/Chatbot/components/Messages/Carousel/ProductSwiper.css +361 -0
  19. package/src/Chatbot/components/Messages/Carousel/TryOn.tsx +2 -2
  20. package/src/Chatbot/components/Messages/ChatMessages.css +32 -1
  21. package/src/Chatbot/components/Messages/ChatMessages.tsx +21 -38
  22. package/src/Chatbot/components/Messages/OrderCard.css +177 -0
  23. package/src/Chatbot/components/Messages/OrderCard.tsx +120 -0
  24. package/src/Chatbot/components/Messages/ProductCard.css +1 -1
  25. package/src/Chatbot/components/Messages/Support.tsx +311 -118
  26. package/src/Chatbot/components/Messages/Typing.css +31 -35
  27. package/src/Chatbot/components/Messages/Typing.tsx +19 -18
  28. package/src/Chatbot/components/Messenger/Messenger.tsx +67 -14
  29. package/src/Chatbot/components/Powered.css +26 -28
  30. package/src/Chatbot/components/Powered.tsx +1 -1
  31. package/src/Chatbot/components/Sessions/CreateSession.tsx +12 -10
  32. package/src/Chatbot/components/TryOn/View.tsx +2 -2
  33. package/src/Chatbot/components/VoiceMode.css +247 -0
  34. package/src/Chatbot/components/VoiceMode.tsx +613 -0
  35. package/src/hooks/config.tsx +9 -10
  36. package/src/hooks/useChatbotAPI.tsx +146 -61
  37. package/src/hooks/useChatbotData.tsx +8 -5
  38. package/src/hooks/useSessionMessages.tsx +0 -1
  39. package/src/hooks/useWebsocke.tsx +40 -18
  40. package/src/i18n.tsx +384 -10
  41. package/src/Chatbot/components/Messages/Carousel/CardSwiperSalla.tsx +0 -187
  42. package/src/Chatbot/components/Messages/Carousel/CardSwiperZid.tsx +0 -208
  43. package/src/Chatbot/components/Messages/Carousel/SallaSwiper.css +0 -245
  44. package/src/Chatbot/components/Messages/SallaAssistantMessage.tsx +0 -202
  45. package/src/Chatbot/components/Messages/ZidAssistantMesage.tsx +0 -203
  46. package/src/Chatbot/components/Sessions/RenderList.tsx +0 -519
@@ -2,8 +2,8 @@ import React from "react";
2
2
  import type { FC } from "react";
3
3
  import "keen-slider/keen-slider.min.css";
4
4
  import type { KeenSliderInstance } from "keen-slider/react";
5
- import "./SallaSwiper.css";
6
- import { Button } from "antd";
5
+ import "./ProductSwiper.css";
6
+ import { useTranslation } from "react-i18next";
7
7
 
8
8
  interface ProductVariant {
9
9
  variant_id: string;
@@ -22,7 +22,7 @@ interface Props {
22
22
  setSelectedVariants: React.Dispatch<React.SetStateAction<Record<string, string>>>;
23
23
  counts: Record<string, number>;
24
24
  setCounts: React.Dispatch<React.SetStateAction<Record<string, number>>>;
25
- handleAddToCart: (variantId: string, name: string, variantImage:string,price:string, qty?: number) => Promise<void>;
25
+ handleAddToCart: (variantId: string, name: string, variantImage: string, price: string, qty?: number) => Promise<void>;
26
26
  handleRemoveFromCart: (variantId: string, qty?: number) => Promise<void>;
27
27
  colorCode?: string;
28
28
  shortenName: (name: string) => string;
@@ -34,7 +34,7 @@ interface Props {
34
34
  currentSlide: number;
35
35
  totalSlides: number;
36
36
  onTryOnSelect: (product: ProductVariant) => void;
37
- enableTry:boolean;
37
+ enableTry: boolean;
38
38
  }
39
39
 
40
40
  const ProductCarousel: FC<Props> = ({
@@ -45,28 +45,25 @@ const ProductCarousel: FC<Props> = ({
45
45
  setCounts,
46
46
  handleAddToCart,
47
47
  handleRemoveFromCart,
48
- colorCode = "#7F28F8",
48
+ colorCode = "#111111",
49
49
  shortenName,
50
50
  sliderRef,
51
51
  totalSlides,
52
52
  disableCheckout = false,
53
53
  onTryOnSelect,
54
- enableTry
55
-
54
+ enableTry,
56
55
  }) => {
56
+ const { t } = useTranslation();
57
57
  if (!totalSlides || totalSlides === 0) return null;
58
58
 
59
- // track which variant is open for Try On
60
-
61
59
  return (
62
60
  <section
63
- className="sallaX"
64
- style={{ "--sallaX-accent": colorCode } as React.CSSProperties}
65
- aria-label="Recommended products"
61
+ className="jwbX"
62
+ style={{ "--jwbX-accent": colorCode } as React.CSSProperties}
63
+ aria-label={t("product.recommended")}
66
64
  >
67
- <div ref={sliderRef} className="keen-slider sallaX-slider">
65
+ <div ref={sliderRef} className="keen-slider jwbX-slider">
68
66
  {Object.entries(groupedProducts).map(([tag, group]) => {
69
- // figure out current active variant within this group
70
67
  const currentVariantId =
71
68
  selectedVariants[tag] || (group.length > 0 ? group[0].variant_id : "");
72
69
  const currentVariant =
@@ -75,7 +72,6 @@ const ProductCarousel: FC<Props> = ({
75
72
 
76
73
  const count = counts[currentVariant.variant_id] || 0;
77
74
 
78
- // Build unique, cleaned list of options (exclude empty/'none')
79
75
  const options = Array.from(
80
76
  new Set(
81
77
  group
@@ -84,7 +80,6 @@ const ProductCarousel: FC<Props> = ({
84
80
  )
85
81
  );
86
82
 
87
- // Update active variant by chosen option
88
83
  const setActiveVariant = (option?: string) => {
89
84
  const match = group.find(v => v.option === option);
90
85
  if (match) {
@@ -95,26 +90,23 @@ const ProductCarousel: FC<Props> = ({
95
90
  }
96
91
  };
97
92
 
98
- // Ensure select has a stable value (fallback to first option)
99
93
  const selectValue = currentVariant.option ?? options[0] ?? "";
100
94
 
101
- const increment = async () => {
102
- setCounts(prev => ({
103
- ...prev,
104
- [currentVariant.variant_id]: (prev[currentVariant.variant_id] || 0) + 1,
105
- }));
106
- try {
107
- await handleAddToCart(currentVariant.variant_id, currentVariant.name,currentVariant.image,currentVariant.price, 1);
108
-
109
- } catch (err: any) {
110
- console.warn("Add to cart failed:", err.message);
111
- setCounts(prev => ({
112
- ...prev,
113
- [currentVariant.variant_id]: (prev[currentVariant.variant_id] || 0) - 1,
114
- }));
115
- }
116
- };
117
-
95
+ const increment = async () => {
96
+ setCounts(prev => ({
97
+ ...prev,
98
+ [currentVariant.variant_id]: (prev[currentVariant.variant_id] || 0) + 1,
99
+ }));
100
+ try {
101
+ await handleAddToCart(currentVariant.variant_id, currentVariant.name, currentVariant.image, currentVariant.price, 1);
102
+ } catch (err: any) {
103
+ console.warn("Add to cart failed:", err.message);
104
+ setCounts(prev => ({
105
+ ...prev,
106
+ [currentVariant.variant_id]: (prev[currentVariant.variant_id] || 0) - 1,
107
+ }));
108
+ }
109
+ };
118
110
 
119
111
  const decrement = async () => {
120
112
  if (count === 0) return;
@@ -128,63 +120,71 @@ const ProductCarousel: FC<Props> = ({
128
120
  await handleRemoveFromCart(currentVariant.variant_id, 1);
129
121
  };
130
122
 
131
-
132
123
  return (
133
124
  <article
134
125
  key={tag}
135
- className="keen-slider__slide sallaX-card"
126
+ className="keen-slider__slide jwbX-card"
136
127
  role="group"
137
128
  aria-roledescription="slide"
138
129
  >
139
- <div className="sallaX-media" onClick={() => window.open(currentVariant.link, "_blank")}>
130
+ <div className="jwbX-media" onClick={() => window.open(currentVariant.link, "_blank")}>
140
131
  <img
141
132
  src={currentVariant.image}
142
133
  alt={currentVariant.name}
143
- className="sallaX-img"
134
+ className="jwbX-img"
144
135
  loading="lazy"
145
136
  />
137
+ <span className="jwbX-mediaShade" />
138
+ <span className="jwbX-priceChip" aria-label={t("product.price")}>
139
+ {currentVariant.price}
140
+ </span>
146
141
  <a
147
142
  href={currentVariant.link}
148
143
  target="_blank"
149
144
  rel="noopener noreferrer"
150
- className="sallaX-visit"
151
- aria-label="Open product page"
145
+ className="jwbX-visit"
146
+ aria-label={t("product.openProduct")}
152
147
  onClick={(e) => e.stopPropagation()}
153
148
  >
154
- View
149
+ {t('product.view')}
155
150
  </a>
156
151
  </div>
157
152
 
158
- <div className="sallaX-body">
153
+ <div className="jwbX-body">
159
154
  <a
160
155
  href={currentVariant.link}
161
156
  target="_blank"
162
157
  rel="noopener noreferrer"
163
- className="sallaX-name"
158
+ className="jwbX-name"
164
159
  title={currentVariant.name}
165
160
  >
166
161
  {shortenName(currentVariant.name)}
167
162
  </a>
168
163
 
169
- <div className="sallaX-row">
170
- <span className="sallaX-price" aria-label="Price">
171
- {currentVariant.price}
172
- </span>
173
- </div>
164
+ {/* Variant picker: chips when few, dropdown when many */}
165
+ {options.length > 1 && options.length <= 3 && (
166
+ <div className="jwbX-chips" role="radiogroup" aria-label={t("product.chooseOption")}>
167
+ {options.map((option) => (
168
+ <button
169
+ key={option}
170
+ type="button"
171
+ role="radio"
172
+ aria-checked={selectValue === option}
173
+ className={`jwbX-chip ${selectValue === option ? "jwbX-chip--active" : ""}`}
174
+ onClick={() => setActiveVariant(option)}
175
+ >
176
+ {option.replace(/^[^:]+:\s*/, "")}
177
+ </button>
178
+ ))}
179
+ </div>
180
+ )}
174
181
 
175
- {/* Options dropdown restored */}
176
- {options.length > 1 && (
177
- <div className="sallaX-selectWrap">
178
- <label
179
- htmlFor={`sallaX-select-${tag}`}
180
- className="sallaX-selectLabel"
181
- >
182
- Choose an option
183
- </label>
182
+ {options.length > 3 && (
183
+ <div className="jwbX-selectWrap">
184
184
  <select
185
- id={`sallaX-select-${tag}`}
186
- className="sallaX-select"
187
- aria-label="Choose an option"
185
+ id={`jwbX-select-${tag}`}
186
+ className="jwbX-select"
187
+ aria-label={t("product.chooseOption")}
188
188
  value={selectValue}
189
189
  onChange={(e) => setActiveVariant(e.target.value)}
190
190
  >
@@ -197,56 +197,51 @@ const ProductCarousel: FC<Props> = ({
197
197
  </div>
198
198
  )}
199
199
 
200
- <div className="flex items-center justify-between w-full space-x-4">
201
- {!disableCheckout && (
202
- <div className="sallaX-cta" aria-label="Quantity controls">
200
+ <div className="jwbX-actions">
201
+ {!disableCheckout && count === 0 && (
202
+ <button
203
+ type="button"
204
+ className="jwbX-add"
205
+ onClick={increment}
206
+ >
207
+ {t('product.addToCart')}
208
+ </button>
209
+ )}
210
+
211
+ {!disableCheckout && count > 0 && (
212
+ <div className="jwbX-cta" aria-label={t("product.qty")}>
203
213
  <button
204
214
  type="button"
205
- className="sallaX-qty sallaX-qty--minus"
215
+ className="jwbX-qty"
206
216
  onClick={decrement}
207
- aria-label="Decrease quantity"
208
- disabled={count === 0}
217
+ aria-label={t("product.decrease")}
209
218
  >
210
219
 
211
220
  </button>
212
- <output
213
- className="sallaX-qtyCount"
214
- aria-live="polite"
215
- aria-atomic="true"
216
- >
221
+ <output className="jwbX-qtyCount" aria-live="polite" aria-atomic="true">
217
222
  {count}
218
223
  </output>
219
224
  <button
220
225
  type="button"
221
- className="sallaX-qty sallaX-qty--plus"
226
+ className="jwbX-qty"
222
227
  onClick={increment}
223
- aria-label="Increase quantity"
228
+ aria-label={t("product.increase")}
224
229
  >
225
230
  +
226
231
  </button>
227
232
  </div>
228
233
  )}
229
234
 
230
- {/* Try On toggle button (kept) */}
231
-
232
- {enableTry?
233
- <Button
234
- style={{ backgroundColor: colorCode, color: "white" }}
235
- className="px-6 py-2 rounded-lg shadow-md transition"
236
- onClick={() => {
237
- onTryOnSelect(currentVariant);
238
- }}
239
- >
240
- Try On
241
- </Button>
242
- :<></>
243
- }
244
-
245
-
235
+ {enableTry && (
236
+ <button
237
+ type="button"
238
+ className="jwbX-try"
239
+ onClick={() => onTryOnSelect(currentVariant)}
240
+ >
241
+ {t('product.tryOn')}
242
+ </button>
243
+ )}
246
244
  </div>
247
-
248
- {/* Try On panel */}
249
-
250
245
  </div>
251
246
  </article>
252
247
  );
@@ -0,0 +1,361 @@
1
+ /* ============================================
2
+ Product cards — compact, dashboard-styled.
3
+ Accent color arrives via --jwbX-accent.
4
+ ============================================ */
5
+ .jwbX {
6
+ --jwbX-accent: #111111;
7
+ --jwbX-bg: #ffffff;
8
+ --jwbX-text: #111827;
9
+ --jwbX-muted: #6b7280;
10
+ --jwbX-border: #ececec;
11
+ --jwbX-shadow: 0 1px 2px rgba(17, 24, 39, 0.05);
12
+
13
+ font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
14
+ color: var(--jwbX-text);
15
+ background: transparent;
16
+ padding: 2px 0;
17
+ }
18
+
19
+ .jwbX-slider {
20
+ padding: 4px 2px 8px;
21
+ }
22
+
23
+ /* ===== Carousel nav (counter + arrows) ===== */
24
+ .jwbX-nav {
25
+ display: flex;
26
+ align-items: center;
27
+ justify-content: space-between;
28
+ margin-bottom: 6px;
29
+ padding-inline: 2px;
30
+ }
31
+
32
+ .jwbX-navCount {
33
+ font-size: 11px;
34
+ font-weight: 700;
35
+ letter-spacing: 0.08em;
36
+ color: #9ca3af;
37
+ font-variant-numeric: tabular-nums;
38
+ }
39
+
40
+ .jwbX-navBtns {
41
+ display: flex;
42
+ gap: 6px;
43
+ }
44
+
45
+ .jwbX-navBtn {
46
+ appearance: none;
47
+ width: 26px;
48
+ height: 26px;
49
+ border-radius: 999px;
50
+ border: 1px solid #ececec;
51
+ background: #ffffff;
52
+ color: #111827;
53
+ font-size: 15px;
54
+ line-height: 1;
55
+ cursor: pointer;
56
+ display: flex;
57
+ align-items: center;
58
+ justify-content: center;
59
+ padding-bottom: 2px;
60
+ transition: all 0.15s ease;
61
+ }
62
+
63
+ .jwbX-navBtn:hover:not(:disabled) {
64
+ border-color: #d1d5db;
65
+ background: #fafafa;
66
+ }
67
+
68
+ .jwbX-navBtn:disabled {
69
+ opacity: 0.35;
70
+ cursor: default;
71
+ }
72
+
73
+ /* ===== Card ===== */
74
+ .jwbX-card {
75
+ background: var(--jwbX-bg);
76
+ border: 1px solid var(--jwbX-border);
77
+ border-radius: 18px;
78
+ overflow: hidden;
79
+ box-shadow: 0 1px 2px rgba(17, 24, 39, 0.04), 0 10px 32px rgba(17, 24, 39, 0.07);
80
+ transition: box-shadow 0.25s ease, transform 0.25s ease, border-color 0.25s ease;
81
+ display: flex;
82
+ flex-direction: column;
83
+ }
84
+
85
+ .jwbX-card:hover {
86
+ transform: translateY(-2px);
87
+ box-shadow: 0 2px 6px rgba(17, 24, 39, 0.06), 0 18px 44px rgba(17, 24, 39, 0.13);
88
+ border-color: #e0e0e0;
89
+ }
90
+
91
+ /* ===== Media ===== */
92
+ .jwbX-media {
93
+ position: relative;
94
+ aspect-ratio: 4 / 3;
95
+ background: #f6f7f9;
96
+ cursor: pointer;
97
+ }
98
+
99
+ .jwbX-img {
100
+ width: 100%;
101
+ height: 100%;
102
+ object-fit: cover;
103
+ display: block;
104
+ transition: transform 0.4s ease;
105
+ }
106
+
107
+ .jwbX-media:hover .jwbX-img {
108
+ transform: scale(1.045);
109
+ }
110
+
111
+ /* soft gradient at the base of the photo — lifts the chips */
112
+ .jwbX-mediaShade {
113
+ position: absolute;
114
+ inset: 0;
115
+ background: linear-gradient(180deg, rgba(17, 24, 39, 0.08), transparent 30%, transparent 62%, rgba(17, 24, 39, 0.22));
116
+ pointer-events: none;
117
+ }
118
+
119
+ .jwbX-priceChip {
120
+ position: absolute;
121
+ left: 10px;
122
+ bottom: 10px;
123
+ font-size: 12.5px;
124
+ font-weight: 800;
125
+ letter-spacing: -0.01em;
126
+ padding: 6px 11px;
127
+ border-radius: 999px;
128
+ background: rgba(255, 255, 255, 0.86);
129
+ backdrop-filter: blur(10px) saturate(180%);
130
+ -webkit-backdrop-filter: blur(10px) saturate(180%);
131
+ border: 1px solid rgba(255, 255, 255, 0.6);
132
+ color: #111827;
133
+ box-shadow: 0 4px 14px rgba(17, 24, 39, 0.18);
134
+ }
135
+
136
+ .jwbX-visit {
137
+ position: absolute;
138
+ right: 10px;
139
+ top: 10px;
140
+ font-size: 11px;
141
+ font-weight: 600;
142
+ line-height: 1;
143
+ padding: 7px 11px;
144
+ border-radius: 999px;
145
+ background: rgba(255, 255, 255, 0.78);
146
+ backdrop-filter: saturate(180%) blur(10px);
147
+ -webkit-backdrop-filter: saturate(180%) blur(10px);
148
+ border: 1px solid rgba(255, 255, 255, 0.6);
149
+ box-shadow: 0 4px 14px rgba(17, 24, 39, 0.14);
150
+ color: var(--jwbX-text);
151
+ text-decoration: none;
152
+ transition: background 0.2s ease, transform 0.2s ease;
153
+ }
154
+
155
+ .jwbX-visit:hover {
156
+ background: #ffffff;
157
+ transform: translateY(-1px);
158
+ }
159
+
160
+ /* ===== Body ===== */
161
+ .jwbX-body {
162
+ padding: 12px;
163
+ display: flex;
164
+ flex-direction: column;
165
+ gap: 10px;
166
+ }
167
+
168
+ .jwbX-name {
169
+ display: block;
170
+ font-weight: 700;
171
+ font-size: 14px;
172
+ letter-spacing: -0.01em;
173
+ color: var(--jwbX-text);
174
+ text-decoration: none;
175
+ white-space: nowrap;
176
+ overflow: hidden;
177
+ text-overflow: ellipsis;
178
+ min-width: 0;
179
+ }
180
+
181
+ .jwbX-name:hover {
182
+ text-decoration: underline;
183
+ text-underline-offset: 2px;
184
+ }
185
+
186
+ /* ===== Variant chips (≤3 options) ===== */
187
+ .jwbX-chips {
188
+ display: flex;
189
+ flex-wrap: wrap;
190
+ gap: 6px;
191
+ }
192
+
193
+ .jwbX-chip {
194
+ appearance: none;
195
+ border: 1px solid var(--jwbX-border);
196
+ background: #ffffff;
197
+ color: var(--jwbX-muted);
198
+ font-size: 12px;
199
+ font-weight: 600;
200
+ padding: 6px 12px;
201
+ border-radius: 999px;
202
+ cursor: pointer;
203
+ transition: all 0.15s ease;
204
+ }
205
+
206
+ .jwbX-chip:hover {
207
+ border-color: #d1d5db;
208
+ color: var(--jwbX-text);
209
+ }
210
+
211
+ .jwbX-chip--active {
212
+ background: var(--jwbX-accent);
213
+ border-color: var(--jwbX-accent);
214
+ color: #ffffff;
215
+ }
216
+
217
+ /* ===== Variant dropdown (4+ options) ===== */
218
+ .jwbX-selectWrap {
219
+ position: relative;
220
+ }
221
+
222
+ .jwbX-select {
223
+ width: 100%;
224
+ appearance: none;
225
+ background: #fafafa;
226
+ border: 1px solid var(--jwbX-border);
227
+ border-radius: 10px;
228
+ padding: 8px 30px 8px 11px;
229
+ font-size: 13px;
230
+ line-height: 1.2;
231
+ color: var(--jwbX-text);
232
+ transition: border-color 0.2s ease, box-shadow 0.2s ease;
233
+ }
234
+
235
+ .jwbX-select:focus {
236
+ outline: none;
237
+ border-color: var(--jwbX-accent);
238
+ box-shadow: 0 0 0 3px rgba(17, 24, 39, 0.08);
239
+ }
240
+
241
+ .jwbX-selectWrap::after {
242
+ content: '▾';
243
+ position: absolute;
244
+ right: 11px;
245
+ top: 50%;
246
+ transform: translateY(-50%);
247
+ pointer-events: none;
248
+ font-size: 11px;
249
+ color: var(--jwbX-muted);
250
+ }
251
+
252
+ /* ===== Actions ===== */
253
+ .jwbX-actions {
254
+ display: flex;
255
+ align-items: center;
256
+ gap: 8px;
257
+ }
258
+
259
+ .jwbX-add {
260
+ flex: 1;
261
+ appearance: none;
262
+ border: none;
263
+ background: linear-gradient(
264
+ 180deg,
265
+ color-mix(in srgb, var(--jwbX-accent) 82%, #6b7280),
266
+ var(--jwbX-accent)
267
+ );
268
+ color: #ffffff;
269
+ font-size: 13px;
270
+ font-weight: 700;
271
+ letter-spacing: 0.02em;
272
+ height: 40px;
273
+ border-radius: 13px;
274
+ cursor: pointer;
275
+ box-shadow: 0 6px 16px color-mix(in srgb, var(--jwbX-accent) 28%, transparent);
276
+ transition: box-shadow 0.15s ease, transform 0.1s ease, filter 0.15s ease;
277
+ }
278
+
279
+ .jwbX-add:hover {
280
+ filter: brightness(1.08);
281
+ box-shadow: 0 8px 22px color-mix(in srgb, var(--jwbX-accent) 38%, transparent);
282
+ }
283
+
284
+ .jwbX-add:active {
285
+ transform: scale(0.98);
286
+ }
287
+
288
+ .jwbX-try {
289
+ appearance: none;
290
+ background: #ffffff;
291
+ border: 1px solid var(--jwbX-border);
292
+ color: var(--jwbX-text);
293
+ font-size: 13px;
294
+ font-weight: 600;
295
+ height: 38px;
296
+ padding: 0 14px;
297
+ border-radius: 12px;
298
+ cursor: pointer;
299
+ transition: border-color 0.15s ease, background 0.15s ease;
300
+ flex-shrink: 0;
301
+ }
302
+
303
+ .jwbX-try:hover {
304
+ border-color: #d1d5db;
305
+ background: #fafafa;
306
+ }
307
+
308
+ /* Quantity stepper (shown once added) */
309
+ .jwbX-cta {
310
+ flex: 1;
311
+ display: flex;
312
+ align-items: center;
313
+ justify-content: space-between;
314
+ background: #fafafa;
315
+ border: 1px solid var(--jwbX-border);
316
+ border-radius: 12px;
317
+ height: 38px;
318
+ padding: 3px;
319
+ user-select: none;
320
+ }
321
+
322
+ .jwbX-qty {
323
+ appearance: none;
324
+ border: none;
325
+ background: var(--jwbX-accent);
326
+ color: #ffffff;
327
+ width: 30px;
328
+ height: 30px;
329
+ border-radius: 9px;
330
+ font-size: 15px;
331
+ font-weight: 600;
332
+ line-height: 1;
333
+ cursor: pointer;
334
+ transition: opacity 0.15s ease, transform 0.1s ease;
335
+ }
336
+
337
+ .jwbX-qty:hover {
338
+ opacity: 0.85;
339
+ }
340
+
341
+ .jwbX-qty:active {
342
+ transform: scale(0.95);
343
+ }
344
+
345
+ .jwbX-qtyCount {
346
+ min-width: 30px;
347
+ text-align: center;
348
+ font-weight: 700;
349
+ font-size: 14px;
350
+ }
351
+
352
+ /* Motion respect */
353
+ @media (prefers-reduced-motion: reduce) {
354
+ .jwbX-card,
355
+ .jwbX-img,
356
+ .jwbX-visit,
357
+ .jwbX-qty,
358
+ .jwbX-add {
359
+ transition: none !important;
360
+ }
361
+ }
@@ -124,7 +124,7 @@ const TryOnBox: React.FC<TryOnProps> = ({ productImage, productID }) => {
124
124
  {/* Upload Box */}
125
125
  {!userImage && !resultImage && !loading && (
126
126
  <label className="flex flex-col items-center justify-center border-2 border-dashed border-gray-300 rounded-xl w-full h-44 cursor-pointer hover:border-purple-500 hover:bg-purple-50 transition">
127
- <CloudUploadOutlined style={{ fontSize: 34, marginBottom: 8, color: '#7F28F8' }} />
127
+ <CloudUploadOutlined style={{ fontSize: 34, marginBottom: 8, color: '#111111' }} />
128
128
  <span className="text-gray-500 font-medium">Click to upload a picture</span>
129
129
  <input
130
130
  type="file"
@@ -138,7 +138,7 @@ const TryOnBox: React.FC<TryOnProps> = ({ productImage, productID }) => {
138
138
  {/* Loader */}
139
139
  {loading && (
140
140
  <div className="flex flex-col items-center justify-center h-44 text-center">
141
- <Spin indicator={<LoadingOutlined style={{ fontSize: 36, color: '#7F28F8' }} spin />} />
141
+ <Spin indicator={<LoadingOutlined style={{ fontSize: 36, color: '#111111' }} spin />} />
142
142
  <p className="mt-2 text-gray-700 font-medium">{loadingMessages[loadingIndex]}</p>
143
143
  </div>
144
144
  )}