Spaces:
Sleeping
Sleeping
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8" /> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"/> | |
| <title>Vibhu Solutions AI Agent</title> | |
| <link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 40 40%22><polygon points=%2220,2 38,20 20,38 2,20%22 fill=%22%230066FF%22/><text x=%2250%25%22 y=%2257%25%22 dominant-baseline=%22middle%22 text-anchor=%22middle%22 fill=%22white%22 font-size=%2213%22 font-weight=%22700%22 font-family=%22sans-serif%22>VS</text></svg>"> | |
| <link rel="preconnect" href="https://fonts.googleapis.com"> | |
| <link href="https://fonts.googleapis.com/css2?family=Manrope:wght@400;500;600;700;800&display=swap" rel="stylesheet"> | |
| <style> | |
| * { box-sizing: border-box; margin: 0; padding: 0; } | |
| body { | |
| font-family: 'Manrope', 'Segoe UI', Tahoma, sans-serif; | |
| min-height: 100vh; | |
| color: white; | |
| background: #000810; | |
| } | |
| /* Vibhu-style hero background */ | |
| body::before { | |
| content: ''; | |
| position: fixed; inset: 0; z-index: 0; pointer-events: none; | |
| background: | |
| radial-gradient(ellipse 65% 60% at 72% 50%, rgba(0,66,200,0.22) 0%, transparent 70%), | |
| radial-gradient(ellipse 30% 40% at 12% 75%, rgba(0,30,130,0.1) 0%, transparent 60%), | |
| linear-gradient(160deg, #000810 0%, #040e22 100%); | |
| } | |
| /* Circuit board grid overlay */ | |
| body::after { | |
| content: ''; | |
| position: fixed; inset: 0; z-index: 0; pointer-events: none; | |
| background-image: | |
| linear-gradient(rgba(0,100,255,0.055) 1px, transparent 1px), | |
| linear-gradient(90deg, rgba(0,100,255,0.055) 1px, transparent 1px); | |
| background-size: 52px 52px; | |
| mask-image: radial-gradient(ellipse 80% 80% at 65% 50%, black 0%, transparent 80%); | |
| -webkit-mask-image: radial-gradient(ellipse 80% 80% at 65% 50%, black 0%, transparent 80%); | |
| } | |
| /* ── Login Overlay ───────────────────────────────────────────── */ | |
| #cb-login-overlay { | |
| position: fixed; inset: 0; | |
| background: rgba(0,4,12,0.45); | |
| backdrop-filter: blur(2px); | |
| display: flex; align-items: center; justify-content: center; | |
| z-index: 99999; | |
| } | |
| #cb-login-card { | |
| background: rgba(4,14,31,0.92); | |
| backdrop-filter: blur(24px); -webkit-backdrop-filter: blur(24px); | |
| border: 1px solid rgba(107,166,255,0.22); | |
| border-radius: 24px; | |
| padding: 44px 38px 36px; | |
| width: 100%; max-width: 420px; margin: 16px; | |
| box-shadow: 0 32px 80px rgba(0,0,0,0.8), 0 0 60px rgba(0,66,200,0.18); | |
| animation: loginSlide 0.35s ease; | |
| position: relative; z-index: 1; | |
| } | |
| @keyframes loginSlide { | |
| from { opacity:0; transform:translateY(28px); } | |
| to { opacity:1; transform:translateY(0); } | |
| } | |
| .login-logo { | |
| width:80px; height:80px; | |
| background: linear-gradient(107.8deg, #6BA6FF 3.37%, #0066FF 95.93%); | |
| border-radius:20px; display:flex; align-items:center; justify-content:center; | |
| margin:0 auto 20px; | |
| box-shadow:0 10px 28px rgba(0,102,255,0.5), 0 0 40px rgba(0,102,255,0.2); | |
| } | |
| #cb-login-card h2 { | |
| text-align:center; color:white; font-size:22px; font-weight:700; | |
| margin-bottom:6px; | |
| } | |
| .login-subtitle { | |
| text-align:center; color:rgba(255,255,255,0.55); font-size:13.5px; | |
| margin-bottom:26px; line-height:1.6; | |
| } | |
| .login-field { margin-bottom:15px; } | |
| .login-field label { | |
| display:block; font-size:13px; font-weight:600; | |
| color:rgba(255,255,255,0.78); margin-bottom:7px; | |
| } | |
| .login-field input { | |
| width:100%; border:1.5px solid rgba(107,166,255,0.22); border-radius:10px; | |
| padding:12px 15px; font-size:14px; color:white; outline:none; | |
| background:rgba(255,255,255,0.07); | |
| transition:border-color 0.2s, box-shadow 0.2s, background 0.2s; | |
| } | |
| .login-field input::placeholder { color:rgba(255,255,255,0.3); } | |
| .login-field input:focus { | |
| border-color:#6BA6FF; | |
| box-shadow:0 0 0 3px rgba(0,102,255,0.18); | |
| background:rgba(107,166,255,0.1); | |
| } | |
| .login-field input.error { border-color:#ef4444; } | |
| #cb-login-btn { | |
| width:100%; padding:14px; | |
| background:linear-gradient(107.8deg, #6BA6FF 3.37%, #0066FF 95.93%); | |
| color:white; border:none; border-radius:10px; | |
| font-size:15px; font-weight:600; cursor:pointer; | |
| margin-top:6px; letter-spacing:0.3px; | |
| transition:opacity 0.2s, transform 0.1s; | |
| box-shadow:0 6px 20px rgba(0,102,255,0.45); | |
| font-family:'Manrope','Segoe UI',sans-serif; | |
| } | |
| #cb-login-btn:hover { opacity:0.91; } | |
| #cb-login-btn:active { transform:scale(0.98); } | |
| .login-privacy { | |
| text-align:center; font-size:11.5px; color:rgba(255,255,255,0.35); margin-top:14px; | |
| } | |
| /* ── Chat Toggle Button ──────────────────────────────────────── */ | |
| #cb-toggle-btn { | |
| position:fixed; bottom:20px; right:20px; | |
| width:48px; height:48px; border-radius:50%; overflow:visible; | |
| background:linear-gradient(107.8deg, #6BA6FF 3.37%, #0066FF 95.93%); | |
| border:none; cursor:pointer; color:white; | |
| box-shadow:0 4px 18px rgba(0,102,255,0.55), 0 0 14px rgba(0,102,255,0.3); | |
| z-index:9999; | |
| display:none; align-items:center; justify-content:center; | |
| } | |
| /* ── Chat Widget ─────────────────────────────────────────────── */ | |
| #cb-widget { | |
| display:none; flex-direction:column; | |
| position:fixed; bottom:78px; right:20px; | |
| width:310px; height:460px; | |
| background:#04111e; | |
| border:1px solid rgba(107,166,255,0.2); | |
| border-radius:16px; | |
| box-shadow:0 12px 48px rgba(0,0,0,0.7), 0 0 30px rgba(0,66,200,0.15); | |
| z-index:9998; overflow:hidden; | |
| animation:widgetPop 0.25s ease; | |
| } | |
| @keyframes widgetPop { | |
| from { opacity:0; transform:scale(0.95) translateY(10px); } | |
| to { opacity:1; transform:scale(1) translateY(0); } | |
| } | |
| /* Header */ | |
| #cb-header { | |
| background:linear-gradient(107.8deg, #6BA6FF 3.37%, #0066FF 95.93%); | |
| color:white; padding:10px 12px; | |
| display:flex; align-items:center; gap:8px; flex-shrink:0; | |
| } | |
| #cb-header .avatar { | |
| width:30px; height:30px; background:rgba(255,255,255,0.22); | |
| border-radius:50%; display:flex; align-items:center; | |
| justify-content:center; flex-shrink:0; | |
| } | |
| #cb-header .info h3 { font-size:12px; font-weight:700; } | |
| #cb-header .info p { font-size:10px; opacity:0.82; margin-top:1px; } | |
| #cb-user-tag { | |
| font-size:11px; background:rgba(255,255,255,0.2); | |
| border-radius:12px; padding:3px 10px; margin-left:auto; | |
| white-space:nowrap; max-width:115px; | |
| overflow:hidden; text-overflow:ellipsis; | |
| } | |
| #cb-status-dot { | |
| width:9px; height:9px; background:#4ade80; | |
| border-radius:50%; flex-shrink:0; | |
| box-shadow:0 0 0 0 rgba(74,222,128,0.5); | |
| animation:pulse 2s infinite; | |
| } | |
| @keyframes pulse { | |
| 0% { box-shadow:0 0 0 0 rgba(74,222,128,0.5); } | |
| 70% { box-shadow:0 0 0 7px rgba(74,222,128,0); } | |
| 100% { box-shadow:0 0 0 0 rgba(74,222,128,0); } | |
| } | |
| /* Messages */ | |
| #cb-messages { | |
| flex:1; overflow-y:auto; padding:10px; | |
| display:flex; flex-direction:column; gap:8px; | |
| background:#030c1a; | |
| scroll-behavior:smooth; | |
| } | |
| #cb-messages::-webkit-scrollbar { width:4px; } | |
| #cb-messages::-webkit-scrollbar-track { background:transparent; } | |
| #cb-messages::-webkit-scrollbar-thumb { background:rgba(107,166,255,0.3); border-radius:2px; } | |
| .cb-msg-wrap { display:flex; flex-direction:column; } | |
| .cb-msg-wrap.user { align-items:flex-end; } | |
| .cb-msg-wrap.bot { flex-direction:row; align-items:flex-end; gap:8px; } | |
| .cb-msg-wrap.bot .cb-msg-col { display:flex; flex-direction:column; align-items:flex-start; } | |
| .cb-bot-mini { | |
| width:22px; height:22px; flex-shrink:0; border-radius:50%; | |
| background:linear-gradient(107.8deg, #6BA6FF 3.37%, #0066FF 95.93%); | |
| display:flex; align-items:center; justify-content:center; align-self:flex-end; | |
| box-shadow:0 2px 6px rgba(0,102,255,0.4); | |
| } | |
| /* Bot Animations */ | |
| .vs-bot-eye { | |
| transform-box:fill-box; transform-origin:50% 50%; | |
| animation:cbBotBlink 4.8s ease-in-out infinite; | |
| } | |
| .vs-bot-eye-r { animation-delay:0.12s; } | |
| @keyframes cbBotBlink { | |
| 0%, 82%, 100% { transform:scaleY(1); } | |
| 84% { transform:scaleY(0.18); } | |
| 86% { transform:scaleY(1); } | |
| 88% { transform:scaleY(0.08); } | |
| 90% { transform:scaleY(1); } | |
| } | |
| .vs-bot-ant { | |
| transform-box:fill-box; transform-origin:50% 50%; | |
| animation:cbAntGlow 2.4s ease-in-out infinite; | |
| } | |
| @keyframes cbAntGlow { | |
| 0%, 100% { opacity:0.4; transform:scale(0.8); filter:drop-shadow(0 0 2px rgba(255,255,255,0.4)); } | |
| 45% { opacity:1; transform:scale(1.15); filter:drop-shadow(0 0 5px white); } | |
| 70% { opacity:0.65; transform:scale(0.9); filter:drop-shadow(0 0 3px rgba(255,255,255,0.55)); } | |
| } | |
| #cb-toggle-btn { animation:cbBotFloat 2s ease-in-out infinite; } | |
| #cb-toggle-btn:hover { animation:none; transform:scale(1.12); } | |
| @keyframes cbBotFloat { | |
| 0%,100% { transform:translateY(0); } | |
| 50% { transform:translateY(-8px); } | |
| } | |
| #cb-toggle-btn::after { | |
| content:''; position:absolute; inset:-4px; border-radius:50%; | |
| border:2px solid rgba(107,166,255,0.6); | |
| animation:cbRingPulse 1.2s ease-out infinite; | |
| } | |
| #cb-toggle-btn::before { | |
| content:''; position:absolute; inset:-10px; border-radius:50%; | |
| border:1.5px solid rgba(107,166,255,0.25); | |
| animation:cbRingPulse 1.2s ease-out infinite 0.4s; | |
| } | |
| @keyframes cbRingPulse { | |
| 0% { transform:scale(1); opacity:0.8; } | |
| 100% { transform:scale(1.35); opacity:0; } | |
| } | |
| .cb-msg { | |
| max-width:86%; padding:8px 11px; border-radius:12px; | |
| font-size:12px; line-height:1.55; | |
| animation:fadeIn 0.22s ease; | |
| } | |
| @keyframes fadeIn { | |
| from { opacity:0; transform:translateY(6px); } | |
| to { opacity:1; transform:none; } | |
| } | |
| .cb-msg.bot { | |
| background:rgba(255,255,255,0.07); color:#d0e8ff; | |
| border:1px solid rgba(107,166,255,0.12); | |
| border-bottom-left-radius:4px; | |
| } | |
| .cb-msg.bot a { | |
| color:#6BA6FF; text-decoration:none; font-weight:500; | |
| } | |
| .cb-msg.bot a:hover { text-decoration:underline; } | |
| .cb-msg.user { | |
| background:linear-gradient(107.8deg, #6BA6FF 3.37%, #0066FF 95.93%); | |
| color:white; border-bottom-right-radius:4px; | |
| } | |
| .cb-time { | |
| font-size:10.5px; color:rgba(255,255,255,0.3); margin-top:4px; padding:0 4px; | |
| } | |
| /* Animated typing dots */ | |
| .cb-msg.typing { | |
| background:rgba(255,255,255,0.07); | |
| border:1px solid rgba(107,166,255,0.12); | |
| border-bottom-left-radius:4px; | |
| padding:13px 16px; | |
| } | |
| .typing-dots { display:flex; gap:5px; align-items:center; } | |
| .typing-dots span { | |
| width:7px; height:7px; background:rgba(107,166,255,0.7); | |
| border-radius:50%; display:inline-block; | |
| animation:dotBounce 1.3s infinite ease-in-out; | |
| } | |
| .typing-dots span:nth-child(2) { animation-delay:0.18s; } | |
| .typing-dots span:nth-child(3) { animation-delay:0.36s; } | |
| @keyframes dotBounce { | |
| 0%,60%,100% { transform:translateY(0); } | |
| 30% { transform:translateY(-8px); } | |
| } | |
| /* Quick replies */ | |
| #cb-quick-replies { | |
| display:flex; flex-wrap:wrap; gap:4px; | |
| padding:0 10px 7px; background:#030c1a; flex-shrink:0; | |
| } | |
| .cb-quick-btn { | |
| background:rgba(0,102,255,0.12); border:1px solid rgba(107,166,255,0.35); color:#6BA6FF; | |
| border-radius:14px; padding:3px 10px; | |
| font-size:10.5px; cursor:pointer; | |
| transition:all 0.18s; white-space:nowrap; | |
| font-family:'Manrope','Segoe UI',sans-serif; | |
| } | |
| .cb-quick-btn:hover { background:#0066FF; color:white; border-color:#0066FF; } | |
| /* Input area */ | |
| #cb-input-area { | |
| display:flex; padding:7px 9px; gap:6px; | |
| border-top:1px solid rgba(107,166,255,0.15); background:#04111e; flex-shrink:0; | |
| } | |
| #cb-input { | |
| flex:1; border:1px solid rgba(107,166,255,0.2); border-radius:18px; | |
| padding:7px 12px; font-size:12px; outline:none; color:white; | |
| background:rgba(255,255,255,0.06); | |
| transition:border-color 0.2s, background 0.2s; resize:none; max-height:60px; | |
| font-family:'Manrope','Segoe UI',sans-serif; | |
| } | |
| #cb-input::placeholder { color:rgba(255,255,255,0.3); } | |
| #cb-input:focus { border-color:#6BA6FF; background:rgba(107,166,255,0.08); } | |
| #cb-send-btn { | |
| width:30px; height:30px; flex-shrink:0; | |
| background:linear-gradient(107.8deg, #6BA6FF 3.37%, #0066FF 95.93%); | |
| border:none; border-radius:50%; | |
| cursor:pointer; color:white; font-size:13px; | |
| display:flex; align-items:center; justify-content:center; | |
| transition:opacity 0.2s, transform 0.1s; | |
| } | |
| #cb-send-btn:hover { opacity:0.88; } | |
| #cb-send-btn:active { transform:scale(0.94); } | |
| #cb-send-btn:disabled { opacity:0.45; cursor:not-allowed; } | |
| /* Mobile — full screen widget */ | |
| @media (max-width: 480px) { | |
| #cb-widget { | |
| bottom:0; right:0; left:0; | |
| width:100%; height:100%; | |
| border-radius:0; | |
| } | |
| #cb-toggle-btn { bottom:16px; right:16px; } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <!-- ════════════ LOGIN OVERLAY ════════════ --> | |
| <div id="cb-login-overlay"> | |
| <div id="cb-login-card"> | |
| <div class="login-logo" id="cb-logo-bot"></div> | |
| <h2>Vibhu Solutions AI</h2> | |
| <p class="login-subtitle"> | |
| Enter your details to chat with our<br>intelligent business AI assistant | |
| </p> | |
| <form id="cb-login-form" onsubmit="cbLogin(event)" novalidate> | |
| <div class="login-field"> | |
| <label>Full Name <span style="color:#ef4444">*</span></label> | |
| <input id="cb-ln" type="text" placeholder="Your full name" | |
| autocomplete="name" required /> | |
| </div> | |
| <div class="login-field"> | |
| <label>Email Address <span style="color:#ef4444">*</span></label> | |
| <input id="cb-le" type="email" placeholder="your@email.com" | |
| autocomplete="email" required /> | |
| </div> | |
| <div class="login-field"> | |
| <label>Phone / WhatsApp | |
| <span style="font-weight:400;color:#9ca3af;font-size:11.5px">(optional)</span> | |
| </label> | |
| <input id="cb-lp" type="tel" placeholder="+91 9XXXXXXXXX" | |
| autocomplete="tel" /> | |
| </div> | |
| <button type="submit" id="cb-login-btn">Start Chatting →</button> | |
| </form> | |
| <p class="login-privacy">🔒 Your information is kept private and secure.</p> | |
| </div> | |
| </div> | |
| <!-- ════════════ CHAT TOGGLE BUTTON ════════════ --> | |
| <button id="cb-toggle-btn" title="Chat with us"></button> | |
| <!-- ════════════ CHAT WIDGET ════════════ --> | |
| <div id="cb-widget"> | |
| <div id="cb-header"> | |
| <div class="avatar" id="cb-header-bot"></div> | |
| <div class="info"> | |
| <h3>Vibhu Solutions AI</h3> | |
| <p>Ask me anything about our services</p> | |
| </div> | |
| <span id="cb-user-tag"></span> | |
| <div id="cb-status-dot"></div> | |
| </div> | |
| <div id="cb-messages"></div> | |
| <div id="cb-quick-replies"> | |
| <button class="cb-quick-btn" | |
| onclick="cbSendQuick('What services do you offer?')">Services</button> | |
| <button class="cb-quick-btn" | |
| onclick="cbSendQuick('How do I start a project with you?')">Start Project</button> | |
| <button class="cb-quick-btn" | |
| onclick="cbSendQuick('How can I contact your team?')">Contact</button> | |
| <button class="cb-quick-btn" | |
| onclick="cbSendQuick('How long does a project take?')">Timeline</button> | |
| </div> | |
| <div id="cb-input-area"> | |
| <textarea id="cb-input" rows="1" | |
| placeholder="Ask anything about our services…"></textarea> | |
| <button id="cb-send-btn" onclick="cbSend()">➤</button> | |
| </div> | |
| </div> | |
| <!-- SCRIPTS --> | |
| <script> | |
| const CB_API_URL = '/chat'; | |
| let cbOpen = false, cbTypingEl = null, cbUser = null; | |
| /* Bot SVG generator */ | |
| const BOT_SVG = (w, h) => `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 56 64" width="${w}" height="${h}"><line x1="28" y1="6" x2="28" y2="14" stroke="rgba(255,255,255,0.85)" stroke-width="3.5" stroke-linecap="round"/><circle cx="28" cy="4" r="4.5" fill="white" class="vs-bot-ant"/><rect x="4" y="12" width="48" height="44" rx="14" fill="rgba(255,255,255,0.18)" stroke="rgba(255,255,255,0.5)" stroke-width="1.5"/><ellipse cx="19" cy="31" rx="7.5" ry="8.5" fill="white" class="vs-bot-eye"/><ellipse cx="37" cy="31" rx="7.5" ry="8.5" fill="white" class="vs-bot-eye vs-bot-eye-r"/><circle cx="19" cy="32" r="4" fill="#0044CC"/><circle cx="37" cy="32" r="4" fill="#0044CC"/><circle cx="21.5" cy="29" r="1.8" fill="rgba(255,255,255,0.75)"/><circle cx="39.5" cy="29" r="1.8" fill="rgba(255,255,255,0.75)"/><path d="M 14 46 Q 28 57 42 46" stroke="white" stroke-width="2.8" fill="none" stroke-linecap="round"/></svg>`; | |
| /* Inject bot into static placeholders */ | |
| document.getElementById('cb-logo-bot').innerHTML = BOT_SVG(50, 56); | |
| document.getElementById('cb-header-bot').innerHTML = BOT_SVG(20, 23); | |
| document.getElementById('cb-toggle-btn').innerHTML = BOT_SVG(26, 29); | |
| /* Format bot message: make links / emails / phones clickable */ | |
| function formatBotMsg(text) { | |
| // Escape HTML to prevent injection | |
| let h = text | |
| .replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>'); | |
| // Bold **text** | |
| h = h.replace(/\*\*([^*\n]+)\*\*/g, '<strong>$1</strong>'); | |
| // URLs | |
| h = h.replace(/(https?:\/\/[^\s&<]+)/g, | |
| '<a href="$1" target="_blank" rel="noopener">$1</a>'); | |
| // Emails | |
| h = h.replace(/([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})/g, | |
| '<a href="mailto:$1">$1</a>'); | |
| // Indian phone numbers | |
| h = h.replace(/(\+91[\s]?[0-9]{10})/g, | |
| '<a href="tel:$1">$1</a>'); | |
| // Line breaks | |
| h = h.replace(/\n/g, '<br>'); | |
| return h; | |
| } | |
| /* Add message bubble */ | |
| function cbAddMsg(text, type, isHTML) { | |
| const msgs = document.getElementById('cb-messages'); | |
| const wrap = document.createElement('div'); | |
| wrap.className = 'cb-msg-wrap ' + type; | |
| if (type === 'bot') { | |
| const mini = document.createElement('div'); | |
| mini.className = 'cb-bot-mini'; | |
| mini.innerHTML = BOT_SVG(14, 16); | |
| wrap.appendChild(mini); | |
| const col = document.createElement('div'); | |
| col.className = 'cb-msg-col'; | |
| const div = document.createElement('div'); | |
| div.className = 'cb-msg bot'; | |
| if (isHTML) div.innerHTML = text; | |
| else div.textContent = text; | |
| col.appendChild(div); | |
| const t = document.createElement('span'); | |
| t.className = 'cb-time'; | |
| t.textContent = new Date().toLocaleTimeString([], {hour:'2-digit', minute:'2-digit'}); | |
| col.appendChild(t); | |
| wrap.appendChild(col); | |
| } else { | |
| const div = document.createElement('div'); | |
| div.className = 'cb-msg ' + type; | |
| if (isHTML) div.innerHTML = text; | |
| else div.textContent = text; | |
| wrap.appendChild(div); | |
| const t = document.createElement('span'); | |
| t.className = 'cb-time'; | |
| t.textContent = new Date().toLocaleTimeString([], {hour:'2-digit', minute:'2-digit'}); | |
| wrap.appendChild(t); | |
| } | |
| msgs.appendChild(wrap); | |
| msgs.scrollTop = msgs.scrollHeight; | |
| return wrap; | |
| } | |
| /* Animated typing indicator */ | |
| function cbShowTyping() { | |
| const msgs = document.getElementById('cb-messages'); | |
| const wrap = document.createElement('div'); | |
| wrap.className = 'cb-msg-wrap bot'; | |
| const mini = document.createElement('div'); | |
| mini.className = 'cb-bot-mini'; | |
| mini.innerHTML = BOT_SVG(14, 16); | |
| wrap.appendChild(mini); | |
| const col = document.createElement('div'); | |
| col.className = 'cb-msg-col'; | |
| const div = document.createElement('div'); | |
| div.className = 'cb-msg typing'; | |
| div.innerHTML = '<div class="typing-dots"><span></span><span></span><span></span></div>'; | |
| col.appendChild(div); | |
| wrap.appendChild(col); | |
| msgs.appendChild(wrap); | |
| msgs.scrollTop = msgs.scrollHeight; | |
| cbTypingEl = wrap; | |
| } | |
| function cbHideTyping() { | |
| if (cbTypingEl) { cbTypingEl.remove(); cbTypingEl = null; } | |
| } | |
| /* Login */ | |
| function cbLogin(e) { | |
| e.preventDefault(); | |
| const nameEl = document.getElementById('cb-ln'); | |
| const emailEl = document.getElementById('cb-le'); | |
| const name = nameEl.value.trim(); | |
| const email = emailEl.value.trim(); | |
| const phone = document.getElementById('cb-lp').value.trim(); | |
| nameEl.classList.remove('error'); | |
| emailEl.classList.remove('error'); | |
| let ok = true; | |
| if (!name) { nameEl.classList.add('error'); nameEl.focus(); ok = false; } | |
| if (!email || !email.includes('@')) { | |
| emailEl.classList.add('error'); | |
| if (ok) emailEl.focus(); | |
| ok = false; | |
| } | |
| if (!ok) return; | |
| cbUser = { | |
| name, email, phone, | |
| session_id: 'vs_' + Date.now() + '_' + Math.random().toString(36).slice(2,9) | |
| }; | |
| try { localStorage.setItem('cb_user', JSON.stringify(cbUser)); } catch(_){} | |
| document.getElementById('cb-login-overlay').style.display = 'none'; | |
| const btn = document.getElementById('cb-toggle-btn'); | |
| btn.style.display = 'flex'; | |
| cbToggle(); | |
| } | |
| /* Restore existing session */ | |
| (function restoreSession() { | |
| try { | |
| const saved = localStorage.getItem('cb_user'); | |
| if (!saved) return; | |
| cbUser = JSON.parse(saved); | |
| document.getElementById('cb-login-overlay').style.display = 'none'; | |
| document.getElementById('cb-toggle-btn').style.display = 'flex'; | |
| } catch(_) { localStorage.removeItem('cb_user'); } | |
| })(); | |
| /* Toggle widget */ | |
| function cbToggle() { | |
| cbOpen = !cbOpen; | |
| const w = document.getElementById('cb-widget'); | |
| const btn = document.getElementById('cb-toggle-btn'); | |
| w.style.display = cbOpen ? 'flex' : 'none'; | |
| btn.innerHTML = cbOpen ? '✕' : BOT_SVG(26, 29); | |
| btn.classList.toggle('open', cbOpen); | |
| if (cbUser && cbUser.name) | |
| document.getElementById('cb-user-tag').textContent = | |
| '👤 ' + cbUser.name.split(' ')[0]; | |
| if (cbOpen && document.getElementById('cb-messages').children.length === 0) { | |
| const fn = cbUser ? cbUser.name.split(' ')[0] : ''; | |
| const greeting = fn | |
| ? `Hi ${fn}! 👋 Welcome to Vibhu Solutions.\n\nI'm your AI assistant. I can help you with:\n• Our services & technologies\n• How to start a project\n• Our process & timelines\n• How to contact our team\n\nWhat can I help you with today?` | |
| : `Hello! 👋 Welcome to Vibhu Solutions.\n\nAsk me about our services, how to get started, or how to contact our team!`; | |
| cbAddMsg(greeting, 'bot', false); | |
| } | |
| if (cbOpen) document.getElementById('cb-input').focus(); | |
| } | |
| document.getElementById('cb-toggle-btn').addEventListener('click', cbToggle); | |
| /* Quick reply */ | |
| function cbSendQuick(msg) { | |
| document.getElementById('cb-input').value = msg; | |
| cbSend(); | |
| } | |
| /* API call with timeout */ | |
| async function cbCallAPI(payload, ms) { | |
| const ctrl = new AbortController(); | |
| const timer = setTimeout(() => ctrl.abort(), ms); | |
| try { | |
| const r = await fetch(CB_API_URL, { | |
| method:'POST', signal:ctrl.signal, | |
| headers:{'Content-Type':'application/json'}, | |
| body: JSON.stringify(payload), | |
| }); | |
| return await r.json(); | |
| } finally { clearTimeout(timer); } | |
| } | |
| /* Send message */ | |
| async function cbSend() { | |
| const input = document.getElementById('cb-input'); | |
| const sendBtn = document.getElementById('cb-send-btn'); | |
| const msg = input.value.trim(); | |
| if (!msg) return; | |
| cbAddMsg(msg, 'user', false); | |
| input.value = ''; | |
| input.style.height = ''; | |
| input.disabled = true; | |
| sendBtn.disabled = true; | |
| cbShowTyping(); | |
| document.getElementById('cb-quick-replies').style.display = 'none'; | |
| const payload = { | |
| message: msg, | |
| session_id: cbUser ? cbUser.session_id : 'guest_' + Date.now(), | |
| user_name: cbUser ? cbUser.name : '', | |
| user_email: cbUser ? cbUser.email : '', | |
| user_phone: cbUser ? cbUser.phone : '', | |
| }; | |
| try { | |
| const data = await cbCallAPI(payload, 30000); | |
| cbHideTyping(); | |
| cbAddMsg(formatBotMsg(data.reply || 'Sorry, something went wrong.'), 'bot', true); | |
| } catch(_) { | |
| cbHideTyping(); | |
| cbTypingEl = cbAddMsg('Please wait, connecting…', 'bot', false); | |
| await new Promise(r => setTimeout(r, 3000)); | |
| cbHideTyping(); | |
| try { | |
| const data = await cbCallAPI(payload, 60000); | |
| cbHideTyping(); | |
| cbAddMsg(formatBotMsg(data.reply || 'Sorry, something went wrong.'), 'bot', true); | |
| } catch(_) { | |
| cbHideTyping(); | |
| cbAddMsg( | |
| 'Unable to reach the assistant right now. Please try again.\n\n' + | |
| '📧 contact@vibhusolutions.com\n📞 +91 9380345108', | |
| 'bot', false | |
| ); | |
| } | |
| } finally { | |
| input.disabled = false; | |
| sendBtn.disabled = false; | |
| input.focus(); | |
| } | |
| } | |
| /* Auto-grow textarea */ | |
| document.getElementById('cb-input').addEventListener('input', function() { | |
| this.style.height = ''; | |
| this.style.height = Math.min(this.scrollHeight, 80) + 'px'; | |
| }); | |
| /* Enter to send (Shift+Enter for newline) */ | |
| document.getElementById('cb-input').addEventListener('keydown', function(e) { | |
| if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); cbSend(); } | |
| }); | |
| </script> | |
| </body> | |
| </html> | |