(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 = `
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();
}
})();