(function() { const API_BASE = 'https://app.deaconai.com'; const styles = ` :host { all: initial; display: block; } * { box-sizing: border-box; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif; } .deacon-container { position: fixed; bottom: 20px; right: 20px; z-index: 999999; } .deacon-bubble { width: 60px; height: 60px; border-radius: 50%; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); box-shadow: 0 4px 12px rgba(0,0,0,0.15); cursor: pointer; display: flex; align-items: center; justify-content: center; transition: transform 0.2s; } .deacon-bubble:hover { transform: scale(1.05); } .deacon-bubble svg { width: 28px; height: 28px; fill: white; } .deacon-window { display: none; position: fixed; bottom: 100px; right: 20px; width: 400px; max-width: calc(100vw - 40px); height: 600px; max-height: calc(100vh - 140px); background: white; border-radius: 16px; box-shadow: 0 8px 24px rgba(0,0,0,0.2); flex-direction: column; overflow: hidden; } .deacon-window.open { display: flex; } .deacon-header { padding: 16px; display: flex; align-items: center; gap: 12px; color: white; position: relative; } .deacon-header-icon { width: 40px; height: 40px; background: rgba(255,255,255,0.2); border-radius: 50%; display: flex; align-items: center; justify-content: center; } .deacon-header-text h1 { margin: 0; font-size: 16px; font-weight: 600; } .deacon-header-text p { margin: 0; font-size: 12px; opacity: 0.8; } .deacon-close { position: absolute; top: 16px; right: 16px; background: rgba(255,255,255,0.2); border: none; width: 28px; height: 28px; border-radius: 50%; cursor: pointer; display: flex; align-items: center; justify-content: center; transition: background 0.2s; } .deacon-close:hover { background: rgba(255,255,255,0.3); } .deacon-close svg { width: 14px; height: 14px; fill: white; } .deacon-messages { flex: 1; overflow-y: auto; padding: 16px; display: flex; flex-direction: column; gap: 12px; } .deacon-message { display: flex; max-width: 80%; } .deacon-message.user { align-self: flex-end; justify-content: flex-end; } .deacon-message-bubble { padding: 12px 16px; border-radius: 16px; font-size: 14px; line-height: 1.4; } .deacon-message.user .deacon-message-bubble { background: #667eea; color: white; border-bottom-right-radius: 4px; } .deacon-message.assistant .deacon-message-bubble { background: #f3f4f6; color: #1f2937; border-bottom-left-radius: 4px; } .deacon-typing { display: none; align-items: center; gap: 4px; padding: 12px 16px; background: #f3f4f6; border-radius: 16px; border-bottom-left-radius: 4px; max-width: 80px; } .deacon-typing.active { display: flex; } .deacon-typing-dot { width: 8px; height: 8px; background: #9ca3af; border-radius: 50%; animation: typing 1.4s infinite; } .deacon-typing-dot:nth-child(2) { animation-delay: 0.2s; } .deacon-typing-dot:nth-child(3) { animation-delay: 0.4s; } @keyframes typing { 0%, 60%, 100% { transform: translateY(0); } 30% { transform: translateY(-10px); } } .deacon-input-area { padding: 16px; border-top: 1px solid #e5e7eb; display: flex; gap: 8px; } .deacon-input { flex: 1; border: 1px solid #d1d5db; border-radius: 24px; padding: 10px 16px; font-size: 14px; outline: none; } .deacon-input:focus { border-color: #667eea; } .deacon-send { width: 40px; height: 40px; border-radius: 50%; background: #667eea; border: none; cursor: pointer; display: flex; align-items: center; justify-content: center; transition: background 0.2s; } .deacon-send:hover { background: #5568d3; } .deacon-send:disabled { background: #d1d5db; cursor: not-allowed; } .deacon-send svg { width: 18px; height: 18px; fill: white; } .deacon-plan-btn { margin: 0 16px 8px; padding: 10px 16px; background: white; border: 2px solid #667eea; color: #667eea; border-radius: 24px; font-size: 13px; font-weight: 600; cursor: pointer; display: flex; align-items: center; justify-content: center; gap: 6px; transition: all 0.2s; } .deacon-plan-btn:hover { background: #667eea; color: white; } .deacon-form { padding: 16px; overflow-y: auto; flex: 1; } .deacon-form h2 { margin: 0 0 8px; font-size: 18px; font-weight: 700; color: #1f2937; } .deacon-form p { margin: 0 0 20px; font-size: 13px; color: #6b7280; } .deacon-form-group { margin-bottom: 16px; } .deacon-form-label { display: block; margin-bottom: 6px; font-size: 13px; font-weight: 600; color: #374151; } .deacon-form-input { width: 100%; border: 1px solid #d1d5db; border-radius: 8px; padding: 10px 12px; font-size: 14px; outline: none; } .deacon-form-input:focus { border-color: #667eea; } .deacon-form-select { width: 100%; border: 1px solid #d1d5db; border-radius: 8px; padding: 10px 12px; font-size: 14px; outline: none; background: white; } .deacon-form-textarea { width: 100%; border: 1px solid #d1d5db; border-radius: 8px; padding: 10px 12px; font-size: 14px; outline: none; min-height: 80px; resize: vertical; } .deacon-form-radio { display: flex; gap: 16px; margin-top: 8px; } .deacon-form-radio label { display: flex; align-items: center; gap: 6px; font-size: 14px; cursor: pointer; } .deacon-form-actions { padding: 16px; border-top: 1px solid #e5e7eb; display: flex; gap: 8px; } .deacon-btn { flex: 1; padding: 12px 20px; border-radius: 8px; font-size: 14px; font-weight: 600; cursor: pointer; transition: all 0.2s; border: none; } .deacon-btn-primary { background: #667eea; color: white; } .deacon-btn-primary:hover { background: #5568d3; } .deacon-btn-secondary { background: #f3f4f6; color: #374151; } .deacon-btn-secondary:hover { background: #e5e7eb; } .deacon-success { display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 40px 20px; text-align: center; flex: 1; } .deacon-success-icon { width: 60px; height: 60px; background: #10b981; border-radius: 50%; display: flex; align-items: center; justify-content: center; margin-bottom: 20px; } .deacon-success-icon svg { width: 32px; height: 32px; fill: white; } .deacon-success h2 { margin: 0 0 8px; font-size: 20px; font-weight: 700; color: #1f2937; } .deacon-success p { margin: 0 0 12px; font-size: 14px; color: #6b7280; } .deacon-hidden { display: none !important; } .deacon-loading { display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 40px 20px; gap: 12px; } .deacon-spinner { width: 40px; height: 40px; border: 3px solid #e5e7eb; border-top-color: #667eea; border-radius: 50%; animation: spin 0.8s linear infinite; } @keyframes spin { to { transform: rotate(360deg); } } .deacon-loading-text { color: #6b7280; font-size: 14px; } @media (max-width: 480px) { .deacon-window { bottom: 90px; right: 10px; left: 10px; width: auto; max-width: none; } .deacon-container { bottom: 10px; right: 10px; } } `; function initWidget(element) { const churchId = element.getAttribute('data-church') || element.getAttribute('data-bot'); if (!churchId) { console.error('Deacon: Missing data-church attribute'); return; } const container = document.createElement('div'); container.className = 'deacon-widget-root'; document.body.appendChild(container); const shadow = container.attachShadow({ mode: 'open' }); const styleSheet = document.createElement('style'); styleSheet.textContent = styles; shadow.appendChild(styleSheet); const html = `

Loading...

Connecting...

Plan Your Visit

Tell us a bit about yourself so we can make your visit special

You're All Set!

`; const wrapper = document.createElement('div'); wrapper.innerHTML = html; shadow.appendChild(wrapper); let church = null; let knowledgeBase = []; let messages = []; let sessionId = null; let currentView = 'chat'; let visitData = { visitor_name: '', email: '', phone: '', preferred_service: '', planned_date: '', party_size: 1, has_children: false, children_ages: '', questions: '' }; const bubble = shadow.getElementById('deacon-bubble'); const windowEl = shadow.getElementById('deacon-window'); const closeBtn = shadow.getElementById('deacon-close'); const messagesEl = shadow.getElementById('deacon-messages'); const input = shadow.getElementById('deacon-input'); const sendBtn = shadow.getElementById('deacon-send'); const header = shadow.getElementById('deacon-header'); const planBtn = shadow.getElementById('plan-visit-btn'); const visitForm = shadow.getElementById('visit-form'); const visitFormActions = shadow.getElementById('visit-form-actions'); const visitSuccess = shadow.getElementById('visit-success'); const inputArea = shadow.querySelector('.deacon-input-area'); async function loadChurchData() { try { const response = await fetch(`${API_BASE}/functions/getChatbotData`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ churchId }) }); if (!response.ok) throw new Error('Failed to load church data'); const data = await response.json(); church = data.church; knowledgeBase = data.knowledgeBase || []; // Hide loading spinner const loading = shadow.getElementById('deacon-loading'); if (loading) loading.remove(); shadow.getElementById('bot-name').textContent = church.bot_name || 'Deacon'; shadow.getElementById('church-name').textContent = church.name; header.style.background = `linear-gradient(135deg, ${church.primary_color || '#667eea'} 0%, ${church.primary_color || '#764ba2'} 100%)`; sessionId = `session_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; const greeting = church.bot_greeting || `Hi! I'm ${church.bot_name || 'Deacon'}, your friendly assistant. How can I help you today?`; addMessage('assistant', greeting); // Show plan button planBtn.classList.remove('deacon-hidden'); // Populate service times const serviceSelect = shadow.getElementById('visit-service'); if (church.service_times && church.service_times.length > 0) { serviceSelect.innerHTML = '' + church.service_times.map(s => `` ).join(''); } else { shadow.getElementById('service-group').style.display = 'none'; } // Set min date for visit date picker const today = new Date().toISOString().split('T')[0]; shadow.getElementById('visit-date').min = today; sendBtn.disabled = false; } catch (error) { console.error('Deacon: Failed to load data', error); const loading = shadow.getElementById('deacon-loading'); if (loading) loading.remove(); addMessage('assistant', "I'm having trouble connecting. Please try again later."); } } function addMessage(role, content) { messages.push({ role, content, timestamp: new Date().toISOString() }); const messageEl = document.createElement('div'); messageEl.className = `deacon-message ${role}`; messageEl.innerHTML = `
${content}
`; messagesEl.appendChild(messageEl); messagesEl.scrollTop = messagesEl.scrollHeight; } function showTyping() { const typing = document.createElement('div'); typing.className = 'deacon-typing active'; typing.id = 'typing-indicator'; typing.innerHTML = '
'; messagesEl.appendChild(typing); messagesEl.scrollTop = messagesEl.scrollHeight; } function hideTyping() { const typing = shadow.getElementById('typing-indicator'); if (typing) typing.remove(); } async function sendMessage() { const message = input.value.trim(); if (!message || !church) return; addMessage('user', message); input.value = ''; sendBtn.disabled = true; showTyping(); try { const kbContext = knowledgeBase.map(item => `${item.title}: ${item.content}`).join('\n\n'); const churchContext = ` Church Name: ${church.name} Address: ${church.address}, ${church.city}, ${church.state} ${church.zip_code} Phone: ${church.phone || 'N/A'} Email: ${church.email || 'N/A'} Website: ${church.website || 'N/A'} Service Times: ${church.service_times?.map(s => `${s.day} at ${s.time} - ${s.name}`).join(', ') || 'N/A'} About: ${church.about || ''} Beliefs: ${church.beliefs || ''} Additional Knowledge: ${kbContext} `; const recentMessages = messages.slice(-6); const response = await fetch(`${API_BASE}/functions/chatWithClaude`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ churchId, messages: recentMessages, botName: church.bot_name || 'Deacon', churchName: church.name, churchContext, knowledgeBase: kbContext, customInstructions: church.bot_custom_instructions || '' }) }); if (!response.ok) throw new Error('Failed to get response'); const data = await response.json(); hideTyping(); addMessage('assistant', data.message); } catch (error) { console.error('Deacon: Message failed', error); hideTyping(); addMessage('assistant', "I'm sorry, I'm having trouble responding right now."); } sendBtn.disabled = false; input.focus(); } bubble.addEventListener('click', () => { windowEl.classList.toggle('open'); if (windowEl.classList.contains('open') && !church) { loadChurchData(); } }); closeBtn.addEventListener('click', () => { windowEl.classList.remove('open'); }); input.addEventListener('keypress', (e) => { if (e.key === 'Enter') sendMessage(); }); sendBtn.addEventListener('click', sendMessage); planBtn.addEventListener('click', () => { currentView = 'plan-visit'; messagesEl.classList.add('deacon-hidden'); inputArea.classList.add('deacon-hidden'); planBtn.classList.add('deacon-hidden'); visitForm.classList.remove('deacon-hidden'); visitFormActions.classList.remove('deacon-hidden'); }); shadow.getElementById('visit-back').addEventListener('click', () => { currentView = 'chat'; visitForm.classList.add('deacon-hidden'); visitFormActions.classList.add('deacon-hidden'); messagesEl.classList.remove('deacon-hidden'); inputArea.classList.remove('deacon-hidden'); planBtn.classList.remove('deacon-hidden'); }); shadow.getElementById('success-back').addEventListener('click', () => { currentView = 'chat'; visitSuccess.classList.add('deacon-hidden'); messagesEl.classList.remove('deacon-hidden'); inputArea.classList.remove('deacon-hidden'); planBtn.classList.add('deacon-hidden'); // Hide plan button after successful submission }); // Toggle children ages field shadow.querySelectorAll('input[name="has-children"]').forEach(radio => { radio.addEventListener('change', (e) => { const childrenAges = shadow.getElementById('children-ages'); if (e.target.value === 'yes') { childrenAges.classList.remove('deacon-hidden'); } else { childrenAges.classList.add('deacon-hidden'); } }); }); shadow.getElementById('visit-submit').addEventListener('click', async () => { const name = shadow.getElementById('visit-name').value.trim(); const email = shadow.getElementById('visit-email').value.trim(); const phone = shadow.getElementById('visit-phone').value.trim(); const date = shadow.getElementById('visit-date').value; const service = shadow.getElementById('visit-service').value; const party = parseInt(shadow.getElementById('visit-party').value) || 1; const hasChildren = shadow.querySelector('input[name="has-children"]:checked').value === 'yes'; const childrenAges = hasChildren ? shadow.getElementById('children-ages').value.trim() : ''; const questions = shadow.getElementById('visit-questions').value.trim(); if (!name || !email || !date) { alert('Please fill in all required fields (Name, Email, and Visit Date)'); return; } try { const response = await fetch(`${API_BASE}/functions/submitVisitPlan`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ churchId, visitor_name: name, email, phone, planned_date: date, preferred_service: service, party_size: party, has_children: hasChildren, children_ages: childrenAges, questions }) }); if (!response.ok) throw new Error('Failed to submit visit plan'); visitForm.classList.add('deacon-hidden'); visitFormActions.classList.add('deacon-hidden'); visitSuccess.classList.remove('deacon-hidden'); shadow.getElementById('success-message').textContent = `We've sent a confirmation to ${email} with everything you need to know. We can't wait to meet you at ${church.name}!`; } catch (error) { console.error('Failed to submit visit plan:', error); alert('Sorry, there was an error submitting your visit plan. Please try again.'); } }); } // Initialize all widgets on the page function initAllWidgets() { const widgets = document.querySelectorAll('.deacon-chat-widget'); if (widgets.length === 0) { // Retry after a short delay if no widgets found setTimeout(initAllWidgets, 100); return; } widgets.forEach(initWidget); } // Try to initialize immediately if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', initAllWidgets); } else { // DOM already loaded initAllWidgets(); } })();