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
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import React, { useState,useCallback } from 'react';
|
|
2
2
|
import { Button, Avatar } from 'antd';
|
|
3
3
|
import { CloseOutlined,ArrowLeftOutlined,ShoppingCartOutlined} from '@ant-design/icons';
|
|
4
|
+
import VoiceMode from '../VoiceMode';
|
|
4
5
|
import '../../Chatbot.css';
|
|
5
6
|
import { useChatbotMessage } from '../../../hooks/useSessionMessages';
|
|
6
7
|
import axios from 'axios';
|
|
@@ -8,6 +9,7 @@ import ChatInput from '../../components/ChatInput';
|
|
|
8
9
|
import Powered from '../../components/Powered';
|
|
9
10
|
import ChatMessages from '../../components/Messages/ChatMessages';
|
|
10
11
|
import config from '../../../hooks/config';
|
|
12
|
+
import { useTranslation } from 'react-i18next';
|
|
11
13
|
import { useChatbotAPI } from '../../../hooks/useChatbotAPI';
|
|
12
14
|
import type { ChatMessage } from '../../../types/chatbot';
|
|
13
15
|
import { getCookie } from '../../../utils/cookies';
|
|
@@ -18,7 +20,6 @@ import { useCart } from '../../../hooks/useCart';
|
|
|
18
20
|
|
|
19
21
|
type MessengerProps = {
|
|
20
22
|
chatbotLogo: string;
|
|
21
|
-
userType:string;
|
|
22
23
|
agentName:string;
|
|
23
24
|
colorCode: string;
|
|
24
25
|
newSession: boolean;
|
|
@@ -27,7 +28,7 @@ type MessengerProps = {
|
|
|
27
28
|
disableForm: boolean;
|
|
28
29
|
sessionId:string;
|
|
29
30
|
setOpen: (v: boolean) => void;
|
|
30
|
-
setView: React.Dispatch<React.SetStateAction<'
|
|
31
|
+
setView: React.Dispatch<React.SetStateAction<'home' | 'create' | 'messenger' | 'cartView' | 'tryOn'>>;
|
|
31
32
|
setNewSession: (v: boolean) => void;
|
|
32
33
|
Opened: boolean;
|
|
33
34
|
calendly:string;
|
|
@@ -52,7 +53,6 @@ const Messenger: React.FC<MessengerProps> = ({
|
|
|
52
53
|
agentName,
|
|
53
54
|
colorCode,
|
|
54
55
|
newSession,
|
|
55
|
-
userType,
|
|
56
56
|
setNewSession,
|
|
57
57
|
messagesSuggestions,
|
|
58
58
|
setMessagesSuggestions,
|
|
@@ -70,6 +70,8 @@ const Messenger: React.FC<MessengerProps> = ({
|
|
|
70
70
|
const [selectedImage, setSelectedImage] = useState<File | null>(null);
|
|
71
71
|
const [audioBlob, setAudioBlob] = useState<Blob | null>(null);
|
|
72
72
|
const [isSending, setIsSending] = useState(false);
|
|
73
|
+
const { t } = useTranslation();
|
|
74
|
+
const [voiceOpen, setVoiceOpen] = useState(false);
|
|
73
75
|
|
|
74
76
|
|
|
75
77
|
|
|
@@ -450,18 +452,59 @@ const socket = useChatSocket(sessionId, handleSocketData);
|
|
|
450
452
|
<Button
|
|
451
453
|
type="text"
|
|
452
454
|
style={{ marginRight: 8 }}
|
|
453
|
-
onClick={() => setView('
|
|
455
|
+
onClick={() => setView('home')}
|
|
454
456
|
icon={<ArrowLeftOutlined />}
|
|
455
457
|
/>
|
|
456
458
|
|
|
457
459
|
{/* Avatar + Title */}
|
|
458
|
-
<
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
460
|
+
<span className="chatbot-avatarWrap">
|
|
461
|
+
<Avatar
|
|
462
|
+
src={
|
|
463
|
+
chatbotLogo ||
|
|
464
|
+
'https://t4.ftcdn.net/jpg/08/90/53/75/360_F_890537572_m7EIYLzXgeTYGDNoIr7KEDOElFUTzFy0.jpg'
|
|
465
|
+
}
|
|
466
|
+
/>
|
|
467
|
+
<span className="chatbot-avatarDot" />
|
|
468
|
+
</span>
|
|
469
|
+
<span className="chatbot-title">
|
|
470
|
+
<span className="chatbot-title-name">{agentName || t('messenger.aiAssistant')}</span>
|
|
471
|
+
<span className="chatbot-title-status">{t('messenger.onlineNow')}</span>
|
|
472
|
+
</span>
|
|
473
|
+
|
|
474
|
+
{/* Voice mode — filled accent button with equalizer motif */}
|
|
475
|
+
<button
|
|
476
|
+
onClick={() => setVoiceOpen(true)}
|
|
477
|
+
aria-label={t('messenger.voiceMode')}
|
|
478
|
+
title={t('messenger.talkToUs')}
|
|
479
|
+
style={{
|
|
480
|
+
marginLeft: 'auto',
|
|
481
|
+
marginRight: 10,
|
|
482
|
+
width: 34,
|
|
483
|
+
height: 34,
|
|
484
|
+
borderRadius: '50%',
|
|
485
|
+
border: 'none',
|
|
486
|
+
background: colorCode || '#111111',
|
|
487
|
+
display: 'flex',
|
|
488
|
+
alignItems: 'center',
|
|
489
|
+
justifyContent: 'center',
|
|
490
|
+
gap: 2.5,
|
|
491
|
+
cursor: 'pointer',
|
|
492
|
+
boxShadow: `0 4px 12px ${colorCode || '#111111'}55`,
|
|
493
|
+
flexShrink: 0,
|
|
494
|
+
}}
|
|
495
|
+
>
|
|
496
|
+
{[9, 15, 11].map((h, i) => (
|
|
497
|
+
<span
|
|
498
|
+
key={i}
|
|
499
|
+
style={{
|
|
500
|
+
width: 3,
|
|
501
|
+
height: h,
|
|
502
|
+
borderRadius: 2,
|
|
503
|
+
background: '#ffffff',
|
|
504
|
+
}}
|
|
505
|
+
/>
|
|
506
|
+
))}
|
|
507
|
+
</button>
|
|
465
508
|
|
|
466
509
|
{items.length > 0 && (
|
|
467
510
|
<div
|
|
@@ -470,7 +513,6 @@ const socket = useChatSocket(sessionId, handleSocketData);
|
|
|
470
513
|
display: "flex",
|
|
471
514
|
alignItems: "center",
|
|
472
515
|
justifyContent: "center",
|
|
473
|
-
marginLeft: "auto",
|
|
474
516
|
marginRight: 12,
|
|
475
517
|
}}
|
|
476
518
|
>
|
|
@@ -498,7 +540,7 @@ const socket = useChatSocket(sessionId, handleSocketData);
|
|
|
498
540
|
position: "absolute",
|
|
499
541
|
top: 2,
|
|
500
542
|
right: 2,
|
|
501
|
-
backgroundColor: colorCode || "#
|
|
543
|
+
backgroundColor: colorCode || "#111111",
|
|
502
544
|
color: "white",
|
|
503
545
|
fontSize: 10,
|
|
504
546
|
fontWeight: 600,
|
|
@@ -549,7 +591,6 @@ const socket = useChatSocket(sessionId, handleSocketData);
|
|
|
549
591
|
agentStatus={agentStatus}
|
|
550
592
|
sessionId={sessionId}
|
|
551
593
|
handleRemoveFromCart={handleRemoveFromCart}
|
|
552
|
-
userType={userType}
|
|
553
594
|
calendly={calendly}
|
|
554
595
|
disableCheckout={disbleCheckout}
|
|
555
596
|
isfetchingMessage={isFetchingMessages}
|
|
@@ -580,6 +621,18 @@ const socket = useChatSocket(sessionId, handleSocketData);
|
|
|
580
621
|
|
|
581
622
|
<Powered />
|
|
582
623
|
</div>
|
|
624
|
+
|
|
625
|
+
{voiceOpen && (
|
|
626
|
+
<VoiceMode
|
|
627
|
+
sessionId={sessionId}
|
|
628
|
+
colorCode={colorCode}
|
|
629
|
+
agentName={agentName}
|
|
630
|
+
newSession={newSession}
|
|
631
|
+
setNewSession={setNewSession}
|
|
632
|
+
onClose={() => setVoiceOpen(false)}
|
|
633
|
+
setMessages={setMessages}
|
|
634
|
+
/>
|
|
635
|
+
)}
|
|
583
636
|
</div>
|
|
584
637
|
);
|
|
585
638
|
};
|
|
@@ -1,29 +1,27 @@
|
|
|
1
1
|
.jawebcss-powered-container {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
}
|
|
29
|
-
|
|
2
|
+
display: inline-flex;
|
|
3
|
+
margin-top: 8px;
|
|
4
|
+
align-items: center;
|
|
5
|
+
gap: 4px;
|
|
6
|
+
font-family: 'Inter', -apple-system, sans-serif;
|
|
7
|
+
font-size: 10px;
|
|
8
|
+
letter-spacing: 0.08em;
|
|
9
|
+
text-transform: uppercase;
|
|
10
|
+
color: #9ca3af;
|
|
11
|
+
padding: 2px 8px;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
.jawebcss-powered-label {
|
|
15
|
+
font-weight: 500;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.jawebcss-powered-link {
|
|
19
|
+
color: #111827;
|
|
20
|
+
font-weight: 700;
|
|
21
|
+
text-decoration: none;
|
|
22
|
+
transition: opacity 0.2s ease;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.jawebcss-powered-link:hover {
|
|
26
|
+
opacity: 0.6;
|
|
27
|
+
}
|
|
@@ -2,7 +2,7 @@ import './Powered.css';
|
|
|
2
2
|
|
|
3
3
|
export default function Powered() {
|
|
4
4
|
return (
|
|
5
|
-
<div className="jawebcss-powered-container">
|
|
5
|
+
<div dir="ltr" className="jawebcss-powered-container">
|
|
6
6
|
<span className="jawebcss-powered-label">Powered by</span>
|
|
7
7
|
<a href="https://jaweb.me/" target="_blank" className="jawebcss-powered-link" rel="noopener noreferrer">
|
|
8
8
|
Jaweb
|
|
@@ -3,6 +3,7 @@ import { Button, Input, Typography, Spin } from 'antd';
|
|
|
3
3
|
import { ArrowLeftOutlined, LoadingOutlined } from '@ant-design/icons';
|
|
4
4
|
import { motion } from 'framer-motion';
|
|
5
5
|
import config from '../../../hooks/config';
|
|
6
|
+
import { useTranslation } from 'react-i18next';
|
|
6
7
|
|
|
7
8
|
const { Title, Text } = Typography;
|
|
8
9
|
|
|
@@ -51,6 +52,7 @@ const CreateSession: React.FC<CreateSessionProps> = ({
|
|
|
51
52
|
visitorId,
|
|
52
53
|
country,
|
|
53
54
|
}) => {
|
|
55
|
+
const { t } = useTranslation();
|
|
54
56
|
const [name, setName] = useState('');
|
|
55
57
|
const [email, setEmail] = useState('');
|
|
56
58
|
const [phone, setPhone] = useState('');
|
|
@@ -109,7 +111,7 @@ const CreateSession: React.FC<CreateSessionProps> = ({
|
|
|
109
111
|
onClick={onBack}
|
|
110
112
|
style={{ marginBottom: '1rem' }}
|
|
111
113
|
>
|
|
112
|
-
|
|
114
|
+
{t('form.back')}
|
|
113
115
|
</Button>
|
|
114
116
|
</motion.div>
|
|
115
117
|
|
|
@@ -121,10 +123,10 @@ const CreateSession: React.FC<CreateSessionProps> = ({
|
|
|
121
123
|
style={{ textAlign: 'center' }}
|
|
122
124
|
>
|
|
123
125
|
<Title level={3} style={{ marginBottom: '2rem' }}>
|
|
124
|
-
|
|
126
|
+
{t('form.welcome')}
|
|
125
127
|
</Title>
|
|
126
128
|
<Text type="secondary">
|
|
127
|
-
|
|
129
|
+
{t('form.shareDetails')}
|
|
128
130
|
</Text>
|
|
129
131
|
</motion.div>
|
|
130
132
|
|
|
@@ -148,23 +150,23 @@ const CreateSession: React.FC<CreateSessionProps> = ({
|
|
|
148
150
|
}}
|
|
149
151
|
>
|
|
150
152
|
<FormField
|
|
151
|
-
label="
|
|
153
|
+
label={t("form.yourName")}
|
|
152
154
|
value={name}
|
|
153
155
|
setter={setName}
|
|
154
|
-
placeholder="
|
|
156
|
+
placeholder={t("form.namePh")}
|
|
155
157
|
/>
|
|
156
158
|
<FormField
|
|
157
|
-
label="
|
|
159
|
+
label={t("form.emailAddress")}
|
|
158
160
|
value={email}
|
|
159
161
|
setter={setEmail}
|
|
160
162
|
type="email"
|
|
161
|
-
placeholder="
|
|
163
|
+
placeholder={t("form.emailPh")}
|
|
162
164
|
/>
|
|
163
165
|
<FormField
|
|
164
|
-
label="
|
|
166
|
+
label={t("form.phoneNumber")}
|
|
165
167
|
value={phone}
|
|
166
168
|
setter={setPhone}
|
|
167
|
-
placeholder="
|
|
169
|
+
placeholder={t("form.phonePh")}
|
|
168
170
|
/>
|
|
169
171
|
|
|
170
172
|
{/* Submit Button */}
|
|
@@ -188,7 +190,7 @@ const CreateSession: React.FC<CreateSessionProps> = ({
|
|
|
188
190
|
}
|
|
189
191
|
/>
|
|
190
192
|
) : (
|
|
191
|
-
'
|
|
193
|
+
t('form.startChat')
|
|
192
194
|
)}
|
|
193
195
|
</Button>
|
|
194
196
|
</motion.div>
|
|
@@ -69,7 +69,7 @@ const TryOnLayout: React.FC<TryOnProps> = ({
|
|
|
69
69
|
|
|
70
70
|
const fileInputRef = useRef<HTMLInputElement>(null);
|
|
71
71
|
|
|
72
|
-
const themeColor = colorCode || '#
|
|
72
|
+
const themeColor = colorCode || '#111111';
|
|
73
73
|
|
|
74
74
|
const activeProduct = product || {
|
|
75
75
|
variant_id: '0',
|
|
@@ -302,7 +302,7 @@ const TryOnLayout: React.FC<TryOnProps> = ({
|
|
|
302
302
|
</>
|
|
303
303
|
);
|
|
304
304
|
|
|
305
|
-
const hideCart =
|
|
305
|
+
const hideCart = userType === 'email';
|
|
306
306
|
const showCartControls = !addToCart && !hideCart && currentState === 'generated' && !isGenerating;
|
|
307
307
|
|
|
308
308
|
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
/* Voice mode — full-widget overlay with premium equalizer bars */
|
|
2
|
+
.jwbV-overlay {
|
|
3
|
+
--jwbV-accent: #111111;
|
|
4
|
+
position: absolute;
|
|
5
|
+
inset: 0;
|
|
6
|
+
z-index: 40;
|
|
7
|
+
background: rgba(255, 255, 255, 0.97);
|
|
8
|
+
backdrop-filter: blur(12px);
|
|
9
|
+
display: flex;
|
|
10
|
+
flex-direction: column;
|
|
11
|
+
align-items: center;
|
|
12
|
+
justify-content: center;
|
|
13
|
+
gap: 16px;
|
|
14
|
+
padding: 24px;
|
|
15
|
+
font-family: 'Inter', -apple-system, sans-serif;
|
|
16
|
+
overflow-y: auto;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.jwbV-close {
|
|
20
|
+
position: absolute;
|
|
21
|
+
top: 14px;
|
|
22
|
+
right: 14px;
|
|
23
|
+
width: 32px;
|
|
24
|
+
height: 32px;
|
|
25
|
+
border-radius: 50%;
|
|
26
|
+
border: 1px solid #ececec;
|
|
27
|
+
background: #ffffff;
|
|
28
|
+
color: #6b7280;
|
|
29
|
+
display: flex;
|
|
30
|
+
align-items: center;
|
|
31
|
+
justify-content: center;
|
|
32
|
+
cursor: pointer;
|
|
33
|
+
transition: all 0.15s ease;
|
|
34
|
+
z-index: 2;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.jwbV-close:hover {
|
|
38
|
+
color: #111827;
|
|
39
|
+
border-color: #d1d5db;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/* ===== Equalizer bars ===== */
|
|
43
|
+
.jwbV-bars {
|
|
44
|
+
border: none;
|
|
45
|
+
background: transparent;
|
|
46
|
+
cursor: pointer;
|
|
47
|
+
display: flex;
|
|
48
|
+
align-items: center;
|
|
49
|
+
justify-content: center;
|
|
50
|
+
gap: 9px;
|
|
51
|
+
height: 130px;
|
|
52
|
+
padding: 20px 28px;
|
|
53
|
+
border-radius: 28px;
|
|
54
|
+
transition: background 0.2s ease;
|
|
55
|
+
flex-shrink: 0;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.jwbV-bars:hover {
|
|
59
|
+
background: rgba(17, 24, 39, 0.03);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.jwbV-bar {
|
|
63
|
+
width: 12px;
|
|
64
|
+
height: 46px;
|
|
65
|
+
border-radius: 8px;
|
|
66
|
+
background: linear-gradient(
|
|
67
|
+
180deg,
|
|
68
|
+
color-mix(in srgb, var(--jwbV-accent) 65%, #ffffff),
|
|
69
|
+
var(--jwbV-accent)
|
|
70
|
+
);
|
|
71
|
+
box-shadow: 0 8px 22px color-mix(in srgb, var(--jwbV-accent) 25%, transparent);
|
|
72
|
+
transform-origin: center;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/* Listening: gentle rolling wave */
|
|
76
|
+
.jwbV-bars--listening .jwbV-bar {
|
|
77
|
+
animation: jwbV-wave 1.5s ease-in-out infinite;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/* Thinking: soft dim pulse, bars settle small */
|
|
81
|
+
.jwbV-bars--thinking .jwbV-bar {
|
|
82
|
+
animation: jwbV-ponder 1.2s ease-in-out infinite;
|
|
83
|
+
filter: saturate(0.55);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/* Speaking: energetic dance — varied rhythm per bar */
|
|
87
|
+
.jwbV-bars--speaking .jwbV-bar {
|
|
88
|
+
animation: jwbV-dance 0.62s ease-in-out infinite;
|
|
89
|
+
}
|
|
90
|
+
.jwbV-bars--speaking .jwbV-bar:nth-child(2n) {
|
|
91
|
+
animation-duration: 0.48s;
|
|
92
|
+
}
|
|
93
|
+
.jwbV-bars--speaking .jwbV-bar:nth-child(3n) {
|
|
94
|
+
animation-duration: 0.72s;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/* Error: flat + grey */
|
|
98
|
+
.jwbV-bars--error .jwbV-bar {
|
|
99
|
+
filter: grayscale(1);
|
|
100
|
+
opacity: 0.45;
|
|
101
|
+
transform: scaleY(0.4);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
@keyframes jwbV-wave {
|
|
105
|
+
0%, 100% { transform: scaleY(0.55); }
|
|
106
|
+
50% { transform: scaleY(1); }
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
@keyframes jwbV-ponder {
|
|
110
|
+
0%, 100% { transform: scaleY(0.35); opacity: 0.7; }
|
|
111
|
+
50% { transform: scaleY(0.5); opacity: 1; }
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
@keyframes jwbV-dance {
|
|
115
|
+
0%, 100% { transform: scaleY(0.35); }
|
|
116
|
+
35% { transform: scaleY(1.25); }
|
|
117
|
+
70% { transform: scaleY(0.7); }
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/* ===== Text ===== */
|
|
121
|
+
.jwbV-status {
|
|
122
|
+
margin: 0;
|
|
123
|
+
font-size: 12px;
|
|
124
|
+
font-weight: 700;
|
|
125
|
+
letter-spacing: 0.08em;
|
|
126
|
+
text-transform: uppercase;
|
|
127
|
+
color: #111827;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
.jwbV-transcript {
|
|
131
|
+
margin: 0;
|
|
132
|
+
font-size: 14px;
|
|
133
|
+
color: #6b7280;
|
|
134
|
+
text-align: center;
|
|
135
|
+
max-width: 300px;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
.jwbV-reply {
|
|
139
|
+
margin: 0;
|
|
140
|
+
font-size: 13px;
|
|
141
|
+
color: #374151;
|
|
142
|
+
text-align: center;
|
|
143
|
+
max-width: 300px;
|
|
144
|
+
line-height: 1.5;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
.jwbV-hint {
|
|
148
|
+
margin: 0;
|
|
149
|
+
font-size: 12px;
|
|
150
|
+
color: #9ca3af;
|
|
151
|
+
text-align: center;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/* ===== Cards inside voice mode ===== */
|
|
155
|
+
.jwbV-payload {
|
|
156
|
+
width: 100%;
|
|
157
|
+
display: flex;
|
|
158
|
+
justify-content: center;
|
|
159
|
+
flex-shrink: 0;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
.jwbV-products {
|
|
163
|
+
gap: 10px;
|
|
164
|
+
overflow-x: auto;
|
|
165
|
+
justify-content: flex-start;
|
|
166
|
+
padding: 4px 2px;
|
|
167
|
+
scrollbar-width: none;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
.jwbV-products::-webkit-scrollbar {
|
|
171
|
+
display: none;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
.jwbV-product {
|
|
175
|
+
flex: 0 0 auto;
|
|
176
|
+
width: 132px;
|
|
177
|
+
background: #ffffff;
|
|
178
|
+
border: 1px solid #ececec;
|
|
179
|
+
border-radius: 14px;
|
|
180
|
+
padding: 8px;
|
|
181
|
+
display: flex;
|
|
182
|
+
flex-direction: column;
|
|
183
|
+
gap: 6px;
|
|
184
|
+
box-shadow: 0 1px 2px rgba(17, 24, 39, 0.05);
|
|
185
|
+
transition: border-color 0.15s ease, transform 0.15s ease;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
.jwbV-product:hover {
|
|
189
|
+
border-color: #d1d5db;
|
|
190
|
+
transform: translateY(-2px);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
.jwbV-productLink {
|
|
194
|
+
text-decoration: none;
|
|
195
|
+
display: flex;
|
|
196
|
+
flex-direction: column;
|
|
197
|
+
gap: 5px;
|
|
198
|
+
min-width: 0;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
.jwbV-add {
|
|
202
|
+
display: flex;
|
|
203
|
+
align-items: center;
|
|
204
|
+
justify-content: center;
|
|
205
|
+
gap: 5px;
|
|
206
|
+
border: none;
|
|
207
|
+
background: var(--jwbV-accent);
|
|
208
|
+
color: #ffffff;
|
|
209
|
+
font-size: 11px;
|
|
210
|
+
font-weight: 700;
|
|
211
|
+
height: 30px;
|
|
212
|
+
border-radius: 9px;
|
|
213
|
+
cursor: pointer;
|
|
214
|
+
transition: opacity 0.15s ease;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
.jwbV-add:hover {
|
|
218
|
+
opacity: 0.85;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
.jwbV-add--done {
|
|
222
|
+
background: #16a34a;
|
|
223
|
+
cursor: default;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
.jwbV-productImg {
|
|
227
|
+
width: 100%;
|
|
228
|
+
aspect-ratio: 1 / 1;
|
|
229
|
+
object-fit: cover;
|
|
230
|
+
border-radius: 9px;
|
|
231
|
+
background: #f6f7f9;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
.jwbV-productName {
|
|
235
|
+
font-size: 11.5px;
|
|
236
|
+
font-weight: 600;
|
|
237
|
+
color: #111827;
|
|
238
|
+
white-space: nowrap;
|
|
239
|
+
overflow: hidden;
|
|
240
|
+
text-overflow: ellipsis;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
.jwbV-productPrice {
|
|
244
|
+
font-size: 11.5px;
|
|
245
|
+
font-weight: 700;
|
|
246
|
+
color: #111827;
|
|
247
|
+
}
|