shopify-chatbot-widget 0.7.8 → 0.8.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.
- package/dist/jaweb-chatbot.css +1 -1
- package/dist/jaweb-chatbot.iife.js +117 -117
- package/package.json +3 -1
- package/src/App.css +42 -0
- package/src/App.tsx +13 -0
- package/src/Chatbot/Chatbot.css +270 -0
- package/src/Chatbot/Chatbot.tsx +214 -0
- package/src/Chatbot/ChatbotTransition.css +19 -0
- package/src/Chatbot/OLDCHAT.tsx +259 -0
- package/src/Chatbot/components/ChatInput.css +127 -0
- package/src/Chatbot/components/ChatInput.tsx +354 -0
- package/src/Chatbot/components/Messages/Addcart.tsx +70 -0
- package/src/Chatbot/components/Messages/AgentStatus.tsx +37 -0
- package/src/Chatbot/components/Messages/AssistantMesage.tsx +206 -0
- package/src/Chatbot/components/Messages/AudioMessage.tsx +123 -0
- package/src/Chatbot/components/Messages/CalendlyEmbeds.tsx +66 -0
- package/src/Chatbot/components/Messages/CardSwiper.tsx +194 -0
- package/src/Chatbot/components/Messages/Carousel/CardSwiper.tsx +264 -0
- package/src/Chatbot/components/Messages/Carousel/CardSwiperSalla.tsx +208 -0
- package/src/Chatbot/components/Messages/Carousel/CardSwiperZid.tsx +208 -0
- package/src/Chatbot/components/Messages/Carousel/SallaSwiper.css +245 -0
- package/src/Chatbot/components/Messages/Carousel/TryOn.tsx +108 -0
- package/src/Chatbot/components/Messages/CartWidget.tsx +151 -0
- package/src/Chatbot/components/Messages/ChatImage.tsx +46 -0
- package/src/Chatbot/components/Messages/ChatImageText.tsx +74 -0
- package/src/Chatbot/components/Messages/ChatImageTextAssis.tsx +72 -0
- package/src/Chatbot/components/Messages/ChatMessages.css +31 -0
- package/src/Chatbot/components/Messages/ChatMessages.tsx +334 -0
- package/src/Chatbot/components/Messages/ProductCard.css +149 -0
- package/src/Chatbot/components/Messages/SallaAssistantMessage.tsx +196 -0
- package/src/Chatbot/components/Messages/Support.tsx +177 -0
- package/src/Chatbot/components/Messages/Typing.css +31 -0
- package/src/Chatbot/components/Messages/Typing.tsx +19 -0
- package/src/Chatbot/components/Messages/ZidAssistantMesage.tsx +196 -0
- package/src/Chatbot/components/Messages/cartwidget.css +123 -0
- package/src/Chatbot/components/Messenger/Messenger.tsx +531 -0
- package/src/Chatbot/components/Notification.tsx +28 -0
- package/src/Chatbot/components/Powered.css +29 -0
- package/src/Chatbot/components/Powered.tsx +12 -0
- package/src/Chatbot/components/Sessions/CreateSession.tsx +201 -0
- package/src/Chatbot/components/Sessions/RenderList.tsx +212 -0
- package/src/Chatbot/components/Suggestions.css +35 -0
- package/src/Chatbot/components/Suggestions.tsx +57 -0
- package/src/Chatbot/notification.css +17 -0
- package/src/assets/react.svg +1 -0
- package/src/chatbot-widget/index.tsx +14 -0
- package/src/hooks/config.tsx +13 -0
- package/src/hooks/useCart.tsx +107 -0
- package/src/hooks/useChatbotAPI.tsx +167 -0
- package/src/hooks/useChatbotData.tsx +132 -0
- package/src/hooks/useSessionMessages.tsx +50 -0
- package/src/hooks/useWebsocke.tsx +45 -0
- package/src/i18n.tsx +30 -0
- package/src/index.css +2 -0
- package/src/main.tsx +10 -0
- package/src/types/chatbot.ts +40 -0
- package/src/utils/cookies.tsx +14 -0
- package/src/vite-env.d.ts +1 -0
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { FC } from 'react';
|
|
3
|
+
import 'keen-slider/keen-slider.min.css';
|
|
4
|
+
import type { KeenSliderInstance } from 'keen-slider/react';
|
|
5
|
+
import './SallaSwiper.css';
|
|
6
|
+
|
|
7
|
+
interface ProductVariant {
|
|
8
|
+
variant_id: string;
|
|
9
|
+
name: string;
|
|
10
|
+
image: string;
|
|
11
|
+
link: string;
|
|
12
|
+
price: string;
|
|
13
|
+
option?: string; // e.g. "Red - XL" or "500ml"
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
interface Props {
|
|
17
|
+
groupedProducts: Record<string, ProductVariant[]>;
|
|
18
|
+
selectedVariants: Record<string, string>;
|
|
19
|
+
setSelectedVariants: React.Dispatch<React.SetStateAction<Record<string, string>>>;
|
|
20
|
+
counts: Record<string, number>;
|
|
21
|
+
setCounts: React.Dispatch<React.SetStateAction<Record<string, number>>>;
|
|
22
|
+
handleAddToCart: (variantId: string, name: string, qty: number) => Promise<void>;
|
|
23
|
+
handleRemoveFromCart: (variantId: string, qty: number) => Promise<void>;
|
|
24
|
+
colorCode?: string; // accent
|
|
25
|
+
shortenName: (name: string) => string;
|
|
26
|
+
|
|
27
|
+
// slider
|
|
28
|
+
sliderRef: React.Ref<HTMLDivElement>;
|
|
29
|
+
sliderInstance: React.MutableRefObject<KeenSliderInstance | null>;
|
|
30
|
+
currentSlide: number;
|
|
31
|
+
totalSlides: number;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const SallaProductCarousel: FC<Props> = ({
|
|
35
|
+
groupedProducts,
|
|
36
|
+
selectedVariants,
|
|
37
|
+
setSelectedVariants,
|
|
38
|
+
counts,
|
|
39
|
+
setCounts,
|
|
40
|
+
handleAddToCart,
|
|
41
|
+
handleRemoveFromCart,
|
|
42
|
+
colorCode = '#7F28F8',
|
|
43
|
+
shortenName,
|
|
44
|
+
sliderRef,
|
|
45
|
+
// sliderInstance, // currently unused
|
|
46
|
+
// currentSlide, // currently unused
|
|
47
|
+
totalSlides,
|
|
48
|
+
}) => {
|
|
49
|
+
if (!totalSlides || totalSlides === 0) return null;
|
|
50
|
+
|
|
51
|
+
return (
|
|
52
|
+
<section
|
|
53
|
+
className="sallaX"
|
|
54
|
+
style={
|
|
55
|
+
{
|
|
56
|
+
// allow live accent theming from prop
|
|
57
|
+
// @ts-ignore
|
|
58
|
+
'--sallaX-accent': colorCode,
|
|
59
|
+
} as React.CSSProperties
|
|
60
|
+
}
|
|
61
|
+
aria-label="Recommended products"
|
|
62
|
+
>
|
|
63
|
+
<div ref={sliderRef} className="keen-slider sallaX-slider">
|
|
64
|
+
{Object.entries(groupedProducts).map(([tag, group]) => {
|
|
65
|
+
const currentVariantId =
|
|
66
|
+
selectedVariants[tag] || (group.length > 0 ? group[0].variant_id : '');
|
|
67
|
+
const currentVariant =
|
|
68
|
+
group.find(v => v.variant_id === currentVariantId) || group[0];
|
|
69
|
+
|
|
70
|
+
if (!currentVariant) return null;
|
|
71
|
+
|
|
72
|
+
const count = counts[currentVariant.variant_id] || 0;
|
|
73
|
+
|
|
74
|
+
// Build unique, cleaned list of options (exclude empty/'none')
|
|
75
|
+
const options = Array.from(
|
|
76
|
+
new Set(
|
|
77
|
+
group
|
|
78
|
+
.map(v => (v.option?.trim() ?? ''))
|
|
79
|
+
.filter(o => o && o.toLowerCase() !== 'none')
|
|
80
|
+
)
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
// Helper to update active variant by option
|
|
84
|
+
const setActiveVariant = (option?: string) => {
|
|
85
|
+
const match = group.find(v => v.option === option);
|
|
86
|
+
if (match) {
|
|
87
|
+
setSelectedVariants(prev => ({
|
|
88
|
+
...prev,
|
|
89
|
+
[tag]: match.variant_id,
|
|
90
|
+
}));
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const increment = async () => {
|
|
95
|
+
setCounts(prev => ({
|
|
96
|
+
...prev,
|
|
97
|
+
[currentVariant.variant_id]: (prev[currentVariant.variant_id] || 0) + 1,
|
|
98
|
+
}));
|
|
99
|
+
await handleAddToCart(currentVariant.variant_id, currentVariant.name, 1);
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const decrement = async () => {
|
|
103
|
+
if (count === 0) return;
|
|
104
|
+
setCounts(prev => ({
|
|
105
|
+
...prev,
|
|
106
|
+
[currentVariant.variant_id]: Math.max(
|
|
107
|
+
0,
|
|
108
|
+
(prev[currentVariant.variant_id] || 0) - 1
|
|
109
|
+
),
|
|
110
|
+
}));
|
|
111
|
+
await handleRemoveFromCart(currentVariant.variant_id, 1);
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
// Ensure the select has a stable value (fallback to first option)
|
|
115
|
+
const selectValue = (currentVariant.option ?? options[0] ?? '');
|
|
116
|
+
|
|
117
|
+
return (
|
|
118
|
+
<article key={tag} className="keen-slider__slide sallaX-card" role="group" aria-roledescription="slide">
|
|
119
|
+
<div className="sallaX-media" onClick={() => window.open(currentVariant.link, '_blank')}>
|
|
120
|
+
<img
|
|
121
|
+
src={currentVariant.image}
|
|
122
|
+
alt={currentVariant.name}
|
|
123
|
+
className="sallaX-img"
|
|
124
|
+
loading="lazy"
|
|
125
|
+
/>
|
|
126
|
+
<a
|
|
127
|
+
href={currentVariant.link}
|
|
128
|
+
target="_blank"
|
|
129
|
+
rel="noopener noreferrer"
|
|
130
|
+
className="sallaX-visit"
|
|
131
|
+
aria-label="Open product page"
|
|
132
|
+
onClick={(e) => e.stopPropagation()}
|
|
133
|
+
>
|
|
134
|
+
View
|
|
135
|
+
</a>
|
|
136
|
+
</div>
|
|
137
|
+
|
|
138
|
+
<div className="sallaX-body">
|
|
139
|
+
<a
|
|
140
|
+
href={currentVariant.link}
|
|
141
|
+
target="_blank"
|
|
142
|
+
rel="noopener noreferrer"
|
|
143
|
+
className="sallaX-name"
|
|
144
|
+
title={currentVariant.name}
|
|
145
|
+
>
|
|
146
|
+
{shortenName(currentVariant.name)}
|
|
147
|
+
</a>
|
|
148
|
+
|
|
149
|
+
<div className="sallaX-row">
|
|
150
|
+
<span className="sallaX-price" aria-label="Price">
|
|
151
|
+
{currentVariant.price}
|
|
152
|
+
</span>
|
|
153
|
+
</div>
|
|
154
|
+
|
|
155
|
+
{/* NEW: Select-based options */}
|
|
156
|
+
{options.length > 0 && (
|
|
157
|
+
<div className="sallaX-selectWrap">
|
|
158
|
+
<label htmlFor={`sallaX-select-${tag}`} className="sallaX-selectLabel">
|
|
159
|
+
Choose an option
|
|
160
|
+
</label>
|
|
161
|
+
<select
|
|
162
|
+
id={`sallaX-select-${tag}`}
|
|
163
|
+
className="sallaX-select"
|
|
164
|
+
aria-label="Choose an option"
|
|
165
|
+
value={selectValue}
|
|
166
|
+
onChange={(e) => setActiveVariant(e.target.value)}
|
|
167
|
+
>
|
|
168
|
+
{options.map((option) => (
|
|
169
|
+
<option key={option} value={option}>
|
|
170
|
+
{option}
|
|
171
|
+
</option>
|
|
172
|
+
))}
|
|
173
|
+
</select>
|
|
174
|
+
</div>
|
|
175
|
+
)}
|
|
176
|
+
|
|
177
|
+
<div className="sallaX-cta" aria-label="Quantity controls">
|
|
178
|
+
<button
|
|
179
|
+
type="button"
|
|
180
|
+
className="sallaX-qty sallaX-qty--minus"
|
|
181
|
+
onClick={decrement}
|
|
182
|
+
aria-label="Decrease quantity"
|
|
183
|
+
disabled={count === 0}
|
|
184
|
+
>
|
|
185
|
+
–
|
|
186
|
+
</button>
|
|
187
|
+
<output className="sallaX-qtyCount" aria-live="polite" aria-atomic="true">
|
|
188
|
+
{count}
|
|
189
|
+
</output>
|
|
190
|
+
<button
|
|
191
|
+
type="button"
|
|
192
|
+
className="sallaX-qty sallaX-qty--plus"
|
|
193
|
+
onClick={increment}
|
|
194
|
+
aria-label="Increase quantity"
|
|
195
|
+
>
|
|
196
|
+
+
|
|
197
|
+
</button>
|
|
198
|
+
</div>
|
|
199
|
+
</div>
|
|
200
|
+
</article>
|
|
201
|
+
);
|
|
202
|
+
})}
|
|
203
|
+
</div>
|
|
204
|
+
</section>
|
|
205
|
+
);
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
export default SallaProductCarousel;
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { FC } from 'react';
|
|
3
|
+
import 'keen-slider/keen-slider.min.css';
|
|
4
|
+
import type { KeenSliderInstance } from 'keen-slider/react';
|
|
5
|
+
import './SallaSwiper.css';
|
|
6
|
+
|
|
7
|
+
interface ProductVariant {
|
|
8
|
+
variant_id: string;
|
|
9
|
+
name: string;
|
|
10
|
+
image: string;
|
|
11
|
+
link: string;
|
|
12
|
+
price: string;
|
|
13
|
+
option?: string; // e.g. "Red - XL" or "500ml"
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
interface Props {
|
|
17
|
+
groupedProducts: Record<string, ProductVariant[]>;
|
|
18
|
+
selectedVariants: Record<string, string>;
|
|
19
|
+
setSelectedVariants: React.Dispatch<React.SetStateAction<Record<string, string>>>;
|
|
20
|
+
counts: Record<string, number>;
|
|
21
|
+
setCounts: React.Dispatch<React.SetStateAction<Record<string, number>>>;
|
|
22
|
+
handleAddToCart: (variantId: string, name: string, qty: number) => Promise<void>;
|
|
23
|
+
handleRemoveFromCart: (variantId: string, qty: number) => Promise<void>;
|
|
24
|
+
colorCode?: string; // accent
|
|
25
|
+
shortenName: (name: string) => string;
|
|
26
|
+
|
|
27
|
+
// slider
|
|
28
|
+
sliderRef: React.Ref<HTMLDivElement>;
|
|
29
|
+
sliderInstance: React.MutableRefObject<KeenSliderInstance | null>;
|
|
30
|
+
currentSlide: number;
|
|
31
|
+
totalSlides: number;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const ZidProductCarousel: FC<Props> = ({
|
|
35
|
+
groupedProducts,
|
|
36
|
+
selectedVariants,
|
|
37
|
+
setSelectedVariants,
|
|
38
|
+
counts,
|
|
39
|
+
setCounts,
|
|
40
|
+
handleAddToCart,
|
|
41
|
+
handleRemoveFromCart,
|
|
42
|
+
colorCode = '#7F28F8',
|
|
43
|
+
shortenName,
|
|
44
|
+
sliderRef,
|
|
45
|
+
// sliderInstance, // currently unused
|
|
46
|
+
// currentSlide, // currently unused
|
|
47
|
+
totalSlides,
|
|
48
|
+
}) => {
|
|
49
|
+
if (!totalSlides || totalSlides === 0) return null;
|
|
50
|
+
|
|
51
|
+
return (
|
|
52
|
+
<section
|
|
53
|
+
className="sallaX"
|
|
54
|
+
style={
|
|
55
|
+
{
|
|
56
|
+
// allow live accent theming from prop
|
|
57
|
+
// @ts-ignore
|
|
58
|
+
'--sallaX-accent': colorCode,
|
|
59
|
+
} as React.CSSProperties
|
|
60
|
+
}
|
|
61
|
+
aria-label="Recommended products"
|
|
62
|
+
>
|
|
63
|
+
<div ref={sliderRef} className="keen-slider sallaX-slider">
|
|
64
|
+
{Object.entries(groupedProducts).map(([tag, group]) => {
|
|
65
|
+
const currentVariantId =
|
|
66
|
+
selectedVariants[tag] || (group.length > 0 ? group[0].variant_id : '');
|
|
67
|
+
const currentVariant =
|
|
68
|
+
group.find(v => v.variant_id === currentVariantId) || group[0];
|
|
69
|
+
|
|
70
|
+
if (!currentVariant) return null;
|
|
71
|
+
|
|
72
|
+
const count = counts[currentVariant.variant_id] || 0;
|
|
73
|
+
|
|
74
|
+
// Build unique, cleaned list of options (exclude empty/'none')
|
|
75
|
+
const options = Array.from(
|
|
76
|
+
new Set(
|
|
77
|
+
group
|
|
78
|
+
.map(v => (v.option?.trim() ?? ''))
|
|
79
|
+
.filter(o => o && o.toLowerCase() !== 'none')
|
|
80
|
+
)
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
// Helper to update active variant by option
|
|
84
|
+
const setActiveVariant = (option?: string) => {
|
|
85
|
+
const match = group.find(v => v.option === option);
|
|
86
|
+
if (match) {
|
|
87
|
+
setSelectedVariants(prev => ({
|
|
88
|
+
...prev,
|
|
89
|
+
[tag]: match.variant_id,
|
|
90
|
+
}));
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const increment = async () => {
|
|
95
|
+
setCounts(prev => ({
|
|
96
|
+
...prev,
|
|
97
|
+
[currentVariant.variant_id]: (prev[currentVariant.variant_id] || 0) + 1,
|
|
98
|
+
}));
|
|
99
|
+
await handleAddToCart(currentVariant.variant_id, currentVariant.name, 1);
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const decrement = async () => {
|
|
103
|
+
if (count === 0) return;
|
|
104
|
+
setCounts(prev => ({
|
|
105
|
+
...prev,
|
|
106
|
+
[currentVariant.variant_id]: Math.max(
|
|
107
|
+
0,
|
|
108
|
+
(prev[currentVariant.variant_id] || 0) - 1
|
|
109
|
+
),
|
|
110
|
+
}));
|
|
111
|
+
await handleRemoveFromCart(currentVariant.variant_id, 1);
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
// Ensure the select has a stable value (fallback to first option)
|
|
115
|
+
const selectValue = (currentVariant.option ?? options[0] ?? '');
|
|
116
|
+
|
|
117
|
+
return (
|
|
118
|
+
<article key={tag} className="keen-slider__slide sallaX-card" role="group" aria-roledescription="slide">
|
|
119
|
+
<div className="sallaX-media" onClick={() => window.open(currentVariant.link, '_blank')}>
|
|
120
|
+
<img
|
|
121
|
+
src={currentVariant.image}
|
|
122
|
+
alt={currentVariant.name}
|
|
123
|
+
className="sallaX-img"
|
|
124
|
+
loading="lazy"
|
|
125
|
+
/>
|
|
126
|
+
<a
|
|
127
|
+
href={currentVariant.link}
|
|
128
|
+
target="_blank"
|
|
129
|
+
rel="noopener noreferrer"
|
|
130
|
+
className="sallaX-visit"
|
|
131
|
+
aria-label="Open product page"
|
|
132
|
+
onClick={(e) => e.stopPropagation()}
|
|
133
|
+
>
|
|
134
|
+
View
|
|
135
|
+
</a>
|
|
136
|
+
</div>
|
|
137
|
+
|
|
138
|
+
<div className="sallaX-body">
|
|
139
|
+
<a
|
|
140
|
+
href={currentVariant.link}
|
|
141
|
+
target="_blank"
|
|
142
|
+
rel="noopener noreferrer"
|
|
143
|
+
className="sallaX-name"
|
|
144
|
+
title={currentVariant.name}
|
|
145
|
+
>
|
|
146
|
+
{shortenName(currentVariant.name)}
|
|
147
|
+
</a>
|
|
148
|
+
|
|
149
|
+
<div className="sallaX-row">
|
|
150
|
+
<span className="sallaX-price" aria-label="Price">
|
|
151
|
+
{currentVariant.price}
|
|
152
|
+
</span>
|
|
153
|
+
</div>
|
|
154
|
+
|
|
155
|
+
{/* NEW: Select-based options */}
|
|
156
|
+
{options.length > 0 && (
|
|
157
|
+
<div className="sallaX-selectWrap">
|
|
158
|
+
<label htmlFor={`sallaX-select-${tag}`} className="sallaX-selectLabel">
|
|
159
|
+
Choose an option
|
|
160
|
+
</label>
|
|
161
|
+
<select
|
|
162
|
+
id={`sallaX-select-${tag}`}
|
|
163
|
+
className="sallaX-select"
|
|
164
|
+
aria-label="Choose an option"
|
|
165
|
+
value={selectValue}
|
|
166
|
+
onChange={(e) => setActiveVariant(e.target.value)}
|
|
167
|
+
>
|
|
168
|
+
{options.map((option) => (
|
|
169
|
+
<option key={option} value={option}>
|
|
170
|
+
{option}
|
|
171
|
+
</option>
|
|
172
|
+
))}
|
|
173
|
+
</select>
|
|
174
|
+
</div>
|
|
175
|
+
)}
|
|
176
|
+
|
|
177
|
+
<div className="sallaX-cta" aria-label="Quantity controls">
|
|
178
|
+
<button
|
|
179
|
+
type="button"
|
|
180
|
+
className="sallaX-qty sallaX-qty--minus"
|
|
181
|
+
onClick={decrement}
|
|
182
|
+
aria-label="Decrease quantity"
|
|
183
|
+
disabled={count === 0}
|
|
184
|
+
>
|
|
185
|
+
–
|
|
186
|
+
</button>
|
|
187
|
+
<output className="sallaX-qtyCount" aria-live="polite" aria-atomic="true">
|
|
188
|
+
{count}
|
|
189
|
+
</output>
|
|
190
|
+
<button
|
|
191
|
+
type="button"
|
|
192
|
+
className="sallaX-qty sallaX-qty--plus"
|
|
193
|
+
onClick={increment}
|
|
194
|
+
aria-label="Increase quantity"
|
|
195
|
+
>
|
|
196
|
+
+
|
|
197
|
+
</button>
|
|
198
|
+
</div>
|
|
199
|
+
</div>
|
|
200
|
+
</article>
|
|
201
|
+
);
|
|
202
|
+
})}
|
|
203
|
+
</div>
|
|
204
|
+
</section>
|
|
205
|
+
);
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
export default ZidProductCarousel;
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
/* Root */
|
|
2
|
+
.sallaX {
|
|
3
|
+
--sallaX-accent: #7F28F8;
|
|
4
|
+
--sallaX-bg: #ffffff;
|
|
5
|
+
--sallaX-text: #0f0f14;
|
|
6
|
+
--sallaX-muted: #6b7280;
|
|
7
|
+
--sallaX-border: rgba(15, 15, 20, 0.08);
|
|
8
|
+
--sallaX-shadow: 0 1px 2px rgba(0,0,0,.06), 0 8px 24px rgba(0,0,0,.08);
|
|
9
|
+
|
|
10
|
+
font-family: ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, system-ui, "Apple Color Emoji", "Segoe UI Emoji";
|
|
11
|
+
color: var(--sallaX-text);
|
|
12
|
+
background: transparent;
|
|
13
|
+
padding: 4px 0;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/* Slider spacing */
|
|
17
|
+
.sallaX-slider {
|
|
18
|
+
padding: 8px 4px 24px;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/* Card */
|
|
22
|
+
.sallaX-card {
|
|
23
|
+
background: var(--sallaX-bg);
|
|
24
|
+
border: 1px solid var(--sallaX-border);
|
|
25
|
+
border-radius: 18px;
|
|
26
|
+
overflow: hidden;
|
|
27
|
+
box-shadow: var(--sallaX-shadow);
|
|
28
|
+
transition: transform .25s ease, box-shadow .25s ease, border-color .25s ease;
|
|
29
|
+
max-width: 520px;
|
|
30
|
+
margin: 0 auto;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.sallaX-card:hover {
|
|
34
|
+
transform: translateY(-2px);
|
|
35
|
+
box-shadow: 0 2px 6px rgba(0,0,0,.08), 0 12px 28px rgba(0,0,0,.12);
|
|
36
|
+
border-color: rgba(15, 15, 20, 0.12);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/* Media */
|
|
40
|
+
.sallaX-media {
|
|
41
|
+
position: relative;
|
|
42
|
+
aspect-ratio: 1 / 1;
|
|
43
|
+
background: #f6f7f9;
|
|
44
|
+
cursor: pointer;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.sallaX-img {
|
|
48
|
+
width: 100%;
|
|
49
|
+
height: 100%;
|
|
50
|
+
object-fit: cover;
|
|
51
|
+
display: block;
|
|
52
|
+
transition: transform .4s ease;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.sallaX-media:hover .sallaX-img {
|
|
56
|
+
transform: scale(1.02);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/* Subtle call-to-action floating button */
|
|
60
|
+
.sallaX-visit {
|
|
61
|
+
position: absolute;
|
|
62
|
+
right: 12px;
|
|
63
|
+
bottom: 12px;
|
|
64
|
+
font-size: 13px;
|
|
65
|
+
line-height: 1;
|
|
66
|
+
padding: 10px 12px;
|
|
67
|
+
border-radius: 999px;
|
|
68
|
+
background: rgba(255,255,255,.75);
|
|
69
|
+
backdrop-filter: saturate(180%) blur(8px);
|
|
70
|
+
border: 1px solid var(--sallaX-border);
|
|
71
|
+
color: var(--sallaX-text);
|
|
72
|
+
text-decoration: none;
|
|
73
|
+
transition: background .2s ease, transform .2s ease;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
.sallaX-visit:hover {
|
|
77
|
+
background: rgba(255,255,255,.95);
|
|
78
|
+
transform: translateY(-1px);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/* Body */
|
|
82
|
+
.sallaX-body {
|
|
83
|
+
padding: 14px 14px 16px;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/* Name */
|
|
87
|
+
.sallaX-name {
|
|
88
|
+
display: -webkit-box;
|
|
89
|
+
-webkit-line-clamp: 2;
|
|
90
|
+
-webkit-box-orient: vertical;
|
|
91
|
+
overflow: hidden;
|
|
92
|
+
text-overflow: ellipsis;
|
|
93
|
+
|
|
94
|
+
font-weight: 600;
|
|
95
|
+
font-size: 16px;
|
|
96
|
+
letter-spacing: -0.01em;
|
|
97
|
+
color: var(--sallaX-text);
|
|
98
|
+
text-decoration: none;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
.sallaX-name:hover {
|
|
102
|
+
text-decoration: underline;
|
|
103
|
+
text-underline-offset: 2px;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/* Price row */
|
|
107
|
+
.sallaX-row {
|
|
108
|
+
display: flex;
|
|
109
|
+
align-items: baseline;
|
|
110
|
+
justify-content: space-between;
|
|
111
|
+
gap: 8px;
|
|
112
|
+
margin-top: 8px;
|
|
113
|
+
margin-bottom: 8px;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
.sallaX-price {
|
|
117
|
+
font-weight: 700;
|
|
118
|
+
font-size: 18px;
|
|
119
|
+
letter-spacing: -0.01em;
|
|
120
|
+
color: var(--sallaX-text);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/* --- NEW: Options (select) --- */
|
|
124
|
+
.sallaX-selectWrap {
|
|
125
|
+
margin: 10px 0 12px;
|
|
126
|
+
position: relative;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
.sallaX-selectLabel {
|
|
130
|
+
font-size: 12px;
|
|
131
|
+
color: var(--sallaX-muted);
|
|
132
|
+
display: inline-block;
|
|
133
|
+
margin-bottom: 6px;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
.sallaX-select {
|
|
137
|
+
width: 100%;
|
|
138
|
+
appearance: none;
|
|
139
|
+
background: #fff;
|
|
140
|
+
border: 1px solid var(--sallaX-border);
|
|
141
|
+
border-radius: 12px;
|
|
142
|
+
padding: 10px 12px;
|
|
143
|
+
font-size: 14px;
|
|
144
|
+
line-height: 1.2;
|
|
145
|
+
color: var(--sallaX-text);
|
|
146
|
+
box-shadow: var(--sallaX-shadow);
|
|
147
|
+
transition: border-color .2s ease, box-shadow .2s ease;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
.sallaX-select:focus {
|
|
151
|
+
outline: none;
|
|
152
|
+
border-color: var(--sallaX-accent);
|
|
153
|
+
box-shadow: 0 0 0 3px rgba(127, 40, 248, .15);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/* Optional: custom caret */
|
|
157
|
+
.sallaX-selectWrap::after {
|
|
158
|
+
content: "▾";
|
|
159
|
+
position: absolute;
|
|
160
|
+
right: 12px;
|
|
161
|
+
top: calc(50% + 8px);
|
|
162
|
+
transform: translateY(-50%);
|
|
163
|
+
pointer-events: none;
|
|
164
|
+
font-size: 12px;
|
|
165
|
+
color: var(--sallaX-muted);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/* (Old chip styles retained but unused) */
|
|
169
|
+
.sallaX-options { display: none; }
|
|
170
|
+
.sallaX-chip { display: none; }
|
|
171
|
+
|
|
172
|
+
/* CTA (quantity controls) */
|
|
173
|
+
.sallaX-cta {
|
|
174
|
+
display: inline-flex;
|
|
175
|
+
align-items: center;
|
|
176
|
+
gap: 2px;
|
|
177
|
+
background: #fff;
|
|
178
|
+
border: 1px solid var(--sallaX-border);
|
|
179
|
+
border-radius: 999px;
|
|
180
|
+
padding: 4px;
|
|
181
|
+
width: max-content;
|
|
182
|
+
user-select: none;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
.sallaX-qty {
|
|
186
|
+
appearance: none;
|
|
187
|
+
border: none;
|
|
188
|
+
background: transparent;
|
|
189
|
+
width: 36px;
|
|
190
|
+
height: 36px;
|
|
191
|
+
border-radius: 999px;
|
|
192
|
+
font-size: 18px;
|
|
193
|
+
font-weight: 600;
|
|
194
|
+
line-height: 36px;
|
|
195
|
+
text-align: center;
|
|
196
|
+
cursor: pointer;
|
|
197
|
+
transition: background .2s ease, transform .1s ease;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
.sallaX-qty[disabled] {
|
|
201
|
+
opacity: .5;
|
|
202
|
+
cursor: not-allowed;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
.sallaX-qty:hover:not([disabled]) {
|
|
206
|
+
background: rgba(15, 15, 20, 0.05);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
.sallaX-qty:active:not([disabled]) {
|
|
210
|
+
transform: scale(0.98);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
.sallaX-qtyCount {
|
|
214
|
+
min-width: 34px;
|
|
215
|
+
text-align: center;
|
|
216
|
+
font-weight: 600;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/* Plus highlighted by accent */
|
|
220
|
+
.sallaX-qty--plus {
|
|
221
|
+
color: #fff;
|
|
222
|
+
background: var(--sallaX-accent);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
.sallaX-qty--plus:hover {
|
|
226
|
+
filter: brightness(0.98);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/* Motion respect */
|
|
230
|
+
@media (prefers-reduced-motion: reduce) {
|
|
231
|
+
.sallaX-card,
|
|
232
|
+
.sallaX-img,
|
|
233
|
+
.sallaX-visit,
|
|
234
|
+
.sallaX-qty {
|
|
235
|
+
transition: none !important;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/* Responsive polish */
|
|
240
|
+
@media (min-width: 768px) {
|
|
241
|
+
.sallaX-body { padding: 16px 16px 18px; }
|
|
242
|
+
.sallaX-name { font-size: 17px; }
|
|
243
|
+
.sallaX-price { font-size: 19px; }
|
|
244
|
+
}
|
|
245
|
+
|