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.
- package/.env.development +2 -0
- package/dist/jaweb-chatbot.css +1 -1
- package/dist/jaweb-chatbot.iife.js +192 -599
- package/home.html +1 -1
- package/jawebDirect.js +1 -1
- package/package.json +1 -1
- package/src/App.tsx +9 -0
- package/src/Chatbot/Chatbot.css +193 -113
- package/src/Chatbot/Chatbot.tsx +26 -19
- package/src/Chatbot/components/Cart/Index.tsx +10 -8
- package/src/Chatbot/components/ChatInput.tsx +11 -11
- package/src/Chatbot/components/Home/Home.css +456 -0
- package/src/Chatbot/components/Home/Home.tsx +293 -0
- package/src/Chatbot/components/Messages/Addcart.tsx +1 -1
- package/src/Chatbot/components/Messages/AssistantMesage.tsx +78 -37
- package/src/Chatbot/components/Messages/CardSwiper.tsx +1 -1
- package/src/Chatbot/components/Messages/Carousel/CardSwiper.tsx +87 -92
- package/src/Chatbot/components/Messages/Carousel/ProductSwiper.css +361 -0
- package/src/Chatbot/components/Messages/Carousel/TryOn.tsx +2 -2
- package/src/Chatbot/components/Messages/ChatMessages.css +32 -1
- package/src/Chatbot/components/Messages/ChatMessages.tsx +21 -38
- package/src/Chatbot/components/Messages/OrderCard.css +177 -0
- package/src/Chatbot/components/Messages/OrderCard.tsx +120 -0
- package/src/Chatbot/components/Messages/ProductCard.css +1 -1
- package/src/Chatbot/components/Messages/Support.tsx +311 -118
- package/src/Chatbot/components/Messages/Typing.css +31 -35
- package/src/Chatbot/components/Messages/Typing.tsx +19 -18
- package/src/Chatbot/components/Messenger/Messenger.tsx +67 -14
- package/src/Chatbot/components/Powered.css +26 -28
- package/src/Chatbot/components/Powered.tsx +1 -1
- package/src/Chatbot/components/Sessions/CreateSession.tsx +12 -10
- package/src/Chatbot/components/TryOn/View.tsx +2 -2
- package/src/Chatbot/components/VoiceMode.css +247 -0
- package/src/Chatbot/components/VoiceMode.tsx +613 -0
- package/src/hooks/config.tsx +9 -10
- package/src/hooks/useChatbotAPI.tsx +146 -61
- package/src/hooks/useChatbotData.tsx +8 -5
- package/src/hooks/useSessionMessages.tsx +0 -1
- package/src/hooks/useWebsocke.tsx +40 -18
- package/src/i18n.tsx +384 -10
- package/src/Chatbot/components/Messages/Carousel/CardSwiperSalla.tsx +0 -187
- package/src/Chatbot/components/Messages/Carousel/CardSwiperZid.tsx +0 -208
- package/src/Chatbot/components/Messages/Carousel/SallaSwiper.css +0 -245
- package/src/Chatbot/components/Messages/SallaAssistantMessage.tsx +0 -202
- package/src/Chatbot/components/Messages/ZidAssistantMesage.tsx +0 -203
- 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 "./
|
|
6
|
-
import {
|
|
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,
|
|
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 = "#
|
|
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="
|
|
64
|
-
style={{ "--
|
|
65
|
-
aria-label="
|
|
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
|
|
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
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
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
|
|
126
|
+
className="keen-slider__slide jwbX-card"
|
|
136
127
|
role="group"
|
|
137
128
|
aria-roledescription="slide"
|
|
138
129
|
>
|
|
139
|
-
<div className="
|
|
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="
|
|
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="
|
|
151
|
-
aria-label="
|
|
145
|
+
className="jwbX-visit"
|
|
146
|
+
aria-label={t("product.openProduct")}
|
|
152
147
|
onClick={(e) => e.stopPropagation()}
|
|
153
148
|
>
|
|
154
|
-
|
|
149
|
+
{t('product.view')}
|
|
155
150
|
</a>
|
|
156
151
|
</div>
|
|
157
152
|
|
|
158
|
-
<div className="
|
|
153
|
+
<div className="jwbX-body">
|
|
159
154
|
<a
|
|
160
155
|
href={currentVariant.link}
|
|
161
156
|
target="_blank"
|
|
162
157
|
rel="noopener noreferrer"
|
|
163
|
-
className="
|
|
158
|
+
className="jwbX-name"
|
|
164
159
|
title={currentVariant.name}
|
|
165
160
|
>
|
|
166
161
|
{shortenName(currentVariant.name)}
|
|
167
162
|
</a>
|
|
168
163
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
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
|
-
{
|
|
176
|
-
|
|
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={`
|
|
186
|
-
className="
|
|
187
|
-
aria-label="
|
|
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="
|
|
201
|
-
{!disableCheckout && (
|
|
202
|
-
<
|
|
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="
|
|
215
|
+
className="jwbX-qty"
|
|
206
216
|
onClick={decrement}
|
|
207
|
-
aria-label="
|
|
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="
|
|
226
|
+
className="jwbX-qty"
|
|
222
227
|
onClick={increment}
|
|
223
|
-
aria-label="
|
|
228
|
+
aria-label={t("product.increase")}
|
|
224
229
|
>
|
|
225
230
|
+
|
|
226
231
|
</button>
|
|
227
232
|
</div>
|
|
228
233
|
)}
|
|
229
234
|
|
|
230
|
-
{
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
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: '#
|
|
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: '#
|
|
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
|
)}
|