(function () { const chatHtml = `
logo
`; function generateUUID() { return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace( /[xy]/g, function (c) { const r = (Math.random() * 16) | 0, v = c === "x" ? r : (r & 0x3) | 0x8; return v.toString(16); } ); } function createBubble(contents, sender) { const bubble = document.createElement("div"); const bubbleClasses = `tulu-bubble-base ${ sender === "user" ? "tulu-bubble-user" : "tulu-bubble-bot" }`; bubble.className = bubbleClasses; bubble.innerHTML = contents; return bubble; } function typeHtmlWordByWord(targetBubble, htmlContent, options = {}) { if (!targetBubble) return; const delay = typeof options.wordDelay === "number" && options.wordDelay >= 0 ? options.wordDelay : 140; const batchSize = typeof options.wordsPerStep === "number" && options.wordsPerStep > 0 ? Math.floor(options.wordsPerStep) : 1; const tempContainer = document.createElement("div"); tempContainer.innerHTML = htmlContent || ""; const tokenSpans = []; const wrapTextNodes = (node) => { const children = Array.from(node.childNodes); children.forEach((child) => { if (child.nodeType === Node.TEXT_NODE) { const text = child.textContent; if (!text) return; const parts = text.match(/(\s+|\S+)/g); if (!parts) return; const frag = document.createDocumentFragment(); parts.forEach((part) => { const span = document.createElement("span"); span.textContent = part; span.style.display = "none"; span.style.whiteSpace = "pre-wrap"; if (/^\s+$/.test(part)) { span.dataset.whitespace = "true"; } frag.appendChild(span); tokenSpans.push(span); }); child.parentNode.replaceChild(frag, child); } else if (child.nodeType === Node.ELEMENT_NODE) { wrapTextNodes(child); } }); }; wrapTextNodes(tempContainer); targetBubble.innerHTML = ""; while (tempContainer.firstChild) { targetBubble.appendChild(tempContainer.firstChild); } if (tokenSpans.length === 0) { targetBubble.innerHTML = htmlContent || ""; if (typeof options.onComplete === "function") options.onComplete(); return; } let index = 0; const total = tokenSpans.length; const maybeScroll = typeof options.onProgress === "function" ? options.onProgress : () => {}; const finish = () => { if (typeof options.onComplete === "function") { options.onComplete(); } }; const revealNext = () => { if (index >= total) { finish(); return; } let shown = 0; while (index < total && shown < batchSize) { const span = tokenSpans[index]; index += 1; span.style.display = "inline"; span.style.whiteSpace = "pre-wrap"; if (span.dataset.whitespace === "true") { continue; } shown += 1; } maybeScroll(); if (index >= total) { finish(); return; } setTimeout(revealNext, delay); }; revealNext(); } function onDomContentLoaded() { console.log("Injecting script"); const rootContainer = document.createElement("div"); rootContainer.innerHTML = chatHtml; document.body.appendChild(rootContainer); // Configure marked library if available, or use fallback let marked; if (window.marked && typeof window.marked.parse === "function") { marked = window.marked; // Configure marked to parse markdown properly if (marked.setOptions) { marked.setOptions({ breaks: true, gfm: true, }); } } else { marked = { parse: (text) => { try { let result = String(text || ""); // First, preserve HTML anchor tags by replacing them with placeholders const anchorPlaceholders = []; let anchorIndex = 0; result = result.replace(/]*>.*?<\/a>/gi, (match) => { const placeholder = `__ANCHOR_${anchorIndex}__`; anchorPlaceholders[anchorIndex] = match; anchorIndex++; return placeholder; }); // Parse markdown bold **text** -> text result = result.replace(/\*\*([^*]+?)\*\*/g, "$1"); // Parse markdown italic *text* -> text (avoid matching **text**) result = result.replace( /(?$1" ); // Now escape HTML but preserve our created tags and anchor placeholders const parts = result.split(/(<\/?strong>|<\/?em>|__ANCHOR_\d+__)/); let escaped = ""; for (let i = 0; i < parts.length; i++) { const part = parts[i]; if (part.match(/^<\/?(strong|em)>$/)) { // Keep our HTML tags as-is escaped += part; } else if (part.match(/^__ANCHOR_(\d+)__$/)) { // Restore anchor tag const idx = parseInt(part.match(/^__ANCHOR_(\d+)__$/)[1]); escaped += anchorPlaceholders[idx] || ""; } else { // Escape HTML in content escaped += part .replace(/&/g, "&") .replace(//g, ">"); } } // Convert newlines to
escaped = escaped.replace(/\n/g, "
"); return escaped; } catch (e) { // If lookbehind not supported, use simpler approach try { let simple = String(text || ""); // Preserve anchor tags const anchorPlaceholders = []; let anchorIndex = 0; simple = simple.replace(/]*>.*?<\/a>/gi, (match) => { const placeholder = `__ANCHOR_${anchorIndex}__`; anchorPlaceholders[anchorIndex] = match; anchorIndex++; return placeholder; }); // Simple bold parsing simple = simple.replace( /\*\*([^*]+?)\*\*/g, "$1" ); // Escape HTML simple = simple .replace(/&/g, "&") .replace(//g, ">"); // Restore our strong tags simple = simple .replace(/<strong>/g, "") .replace(/<\/strong>/g, ""); // Restore anchor tags for (let i = 0; i < anchorPlaceholders.length; i++) { simple = simple.replace( `__ANCHOR_${i}__`, anchorPlaceholders[i] ); } simple = simple.replace(/\n/g, "
"); return simple; } catch (_) { return String(text || ""); } } }, }; } let centralChatExpanded = false; const chatId = "8d926ae3-e195-460e-bbdc-b08f2715ea3b"; const projectKey = "3789e571-80c1-470f-a900-b4ebb5ca62f9"; // Medanta Analytics Tracking Function function logMedantaAnalyticsEvent(eventType, eventData = {}) { if (!isMedantaClient) return; // Only track for Medanta instance // Get current chat ID (use session UUID if chatId is not available) let sessionUUID = sessionStorage.getItem("sessionUUID"); if (!sessionUUID) { sessionUUID = generateUUID(); sessionStorage.setItem("sessionUUID", sessionUUID); } const currentChatId = chatId && chatId !== "8d926ae3-e195-460e-bbdc-b08f2715ea3b" ? chatId : sessionUUID; // Send analytics event to backend (fire and forget) fetch("https://main.tuluhealth.com/v1/analytics/medanta/event/", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ chat_id: currentChatId, project_id: projectKey, event_type: eventType, event_data: eventData, }), }).catch((err) => { // Silently fail - analytics should not break the user experience console.debug("Analytics tracking failed:", err); }); } // Mobile detection const isMobile = window.innerWidth <= 768 || /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test( navigator.userAgent ); // Instances that should default to CTA on mobile (ruban) const instanceKey = "medantabot"; const isRubanClient = instanceKey === "ruban"; const isDefaultCtaOnMobile = isMobile && isRubanClient; // Enable inactivity nudge only for MOC const isMocClient = "medantabot" === "moc"; // Medanta-specific: enforce buttons-only UI const isMedantaClient = instanceKey === "medantabot"; // Medanta-specific: auto-FAQ control let medantaFaqShown = false; // Medanta-specific: flag to suppress option rendering while in progressive FAQ flow let medantaFaqInProgress = false; let medantaQuestionStep = 0; const cahoAssistantSmallButton = document.getElementById( "cahoAssistantSmallButton" ); const chatbotContainer = document.getElementById("chatbotContainer"); const centralChatCloseButton = document.getElementById( "centralChatCloseButton" ); const centralChatEnlargeButton = document.getElementById( "centralChatEnlargeButton" ); const whatsappTopButton = document.getElementById("whatsappTopButton"); const sendMessageButton = document.getElementById("sendMessageButton"); const sendMessageButtonSvg = document.getElementById( "sendMessageButtonSvg" ); const microphoneButton = document.getElementById("microphoneButton"); const textInput = document.getElementById("textInput"); const textInputBar = document.getElementById("textInputBar"); const chatMessagesListContainer = document.getElementById( "chatMessagesListContainer" ); const botDescriptionAndSuggestionsContainer = document.getElementById( "botDescriptionAndSuggestionsContainer" ); const optionButtonsContainer = document.getElementById( "optionButtonsContainer" ); let lastInlineOptionsContainer = null; const topLogosContainer = document.querySelector( "#chatbotContainer > div:first-child" ); const contractSvgIcon = document.getElementById("contractSvgIcon"); const expandSvgIcon = document.getElementById("expandSvgIcon"); // Inactivity timer setup (MOC only, 2 minutes) let inactivityTimer = null; let inactivityMessageSent = false; const INACTIVITY_MS = 120 * 1000; // Medanta intro guard let medantaIntroShown = false; function appendInactivityMessage() { if (!isMocClient) return; if (inactivityMessageSent) return; // Auto-open expanded chat to show the nudge message // First ensure the main container is visible (not in CTA button state) if (cahoAssistantSmallButton.style.display === "block") { cahoAssistantSmallButton.style.setProperty( "display", "none", "important" ); chatbotContainer.style.setProperty("display", "flex", "important"); } // Then expand if not already expanded if (!centralChatExpanded) { handleChatWidgetEnlargeOrContract(); } const callbackText = "We're here to help. For any assistance, please call +91 9769709229 or WhatsApp us."; const botBubble = createBubble(callbackText, "bot"); chatMessagesListContainer.appendChild(botBubble); inactivityMessageSent = true; scrollToBottom(); } function startOrResetInactivityTimer() { if (!isMocClient) return; if (inactivityTimer) clearTimeout(inactivityTimer); inactivityTimer = setTimeout(appendInactivityMessage, INACTIVITY_MS); } // Initialize in collapsed state on page load centralChatExpanded = false; chatMessagesListContainer.style.setProperty("display", "none", "important"); contractSvgIcon.classList.add("tulu-hidden"); expandSvgIcon.classList.remove("tulu-hidden"); topLogosContainer.style.marginBottom = "0px"; // Inactivity timer now starts only after first user message or bot reply (no initial start) // Special default state for selected clients on mobile - show CTA button instead of center container if (isDefaultCtaOnMobile) { cahoAssistantSmallButton.style.setProperty( "display", "block", "important" ); chatbotContainer.style.setProperty("display", "none", "important"); } else { cahoAssistantSmallButton.style.setProperty( "display", "none", "important" ); chatbotContainer.style.setProperty("display", "flex", "important"); } // Medanta: open expanded by default on page load (no click needed) if (isMedantaClient) { try { cahoAssistantSmallButton.style.setProperty( "display", "none", "important" ); chatbotContainer.style.setProperty("display", "flex", "important"); if (!centralChatExpanded) { // Use requestAnimationFrame to ensure DOM is ready before expanding requestAnimationFrame(() => { handleChatWidgetEnlargeOrContract(); }); } } catch (_) {} } // Desktop vs mobile placeholder hint (disabled for Medanta) if (textInput && !isMedantaClient) { if (isMobile) { textInput.placeholder = "Ask your queries here..."; } else { textInput.placeholder = "Ask your queries here... (Shift+Enter for new line)"; } } // Show WhatsApp top-right button only for MOC instance if (isMocClient && whatsappTopButton) { whatsappTopButton.style.setProperty( "display", "inline-block", "important" ); } else if (whatsappTopButton) { whatsappTopButton.style.setProperty("display", "none", "important"); } // Medanta-specific welcome copy change and hide quick suggestions if (isMedantaClient && botDescriptionAndSuggestionsContainer) { const welcomeParas = botDescriptionAndSuggestionsContainer.querySelectorAll( ".tulu-welcome-text" ); if (welcomeParas && welcomeParas[0]) { welcomeParas[0].style.setProperty("display", "none", "important"); } if (welcomeParas && welcomeParas[1]) { welcomeParas[1].style.setProperty("display", "none", "important"); } const suggestions = botDescriptionAndSuggestionsContainer.querySelector( "#suggestionsContainer" ); if (suggestions) { suggestions.style.setProperty("display", "none", "important"); } } function removeBotDescriptionAndSuggestionsContainer() { botDescriptionAndSuggestionsContainer.style.display = "none"; } function clearOptionButtons() { if (lastInlineOptionsContainer && lastInlineOptionsContainer.parentNode) { lastInlineOptionsContainer.parentNode.removeChild( lastInlineOptionsContainer ); } lastInlineOptionsContainer = null; if (!optionButtonsContainer) return; optionButtonsContainer.innerHTML = ""; optionButtonsContainer.style.display = "none"; } function createOptionButton(label, onClickOverride) { const btn = document.createElement("button"); const hasCustomHandler = typeof onClickOverride === "function"; // Avoid the global suggestions handler for Medanta when we have a custom handler if (!isMedantaClient || !hasCustomHandler) { btn.className = "suggestionsButton tulu-border tulu-border-8ec5ff tulu-rounded-full tulu-text-155dfc tulu-px-3 tulu-py-1 tulu-bg-white tulu-hover-scale-105 tulu-transition-all tulu-cursor-pointer"; } btn.textContent = label.trim(); // Medanta-specific: force orange primary style for option buttons if (isMedantaClient) { btn.style.border = "none"; btn.style.borderRadius = "9999px"; btn.style.padding = "10px 16px"; btn.style.fontSize = "1.02em"; btn.style.fontWeight = "600"; btn.style.cursor = "pointer"; btn.style.backgroundColor = "#1e5a87"; btn.style.color = "#ffffff"; btn.style.boxShadow = "0 2px 6px rgba(0,0,0,0.08)"; } btn.addEventListener("click", (e) => { e.stopPropagation(); if (hasCustomHandler) { if (e.stopImmediatePropagation) e.stopImmediatePropagation(); onClickOverride(); } else { sendMessage(label.trim()); startOrResetInactivityTimer(); } }); return btn; } // Extract option lines like "(a) Yes" or list bullets "- English" from latest bot message function renderOptionButtonsFrom(botText, afterElement) { if (!isMedantaClient) return; // Allow rendering buttons even if share info lines are present clearOptionButtons(); // If LLM response contains social links, replace those lines with icon row inside the same bubble const hasSocialLinks = /(https?:\/\/)?x\.com\/medanta/i.test(botText) || /(https?:\/\/)?(www\.)?instagram\.com\/medanta/i.test(botText) || /(https?:\/\/)?(www\.)?facebook\.com\/medanta/i.test(botText) || /We are so glad you took this step towards early detection/i.test( botText ) || //i.test(botText) || /Thank you for taking this assessment\.?/i.test(botText); if (hasSocialLinks && afterElement) { injectSocialIconsIntoBubble(afterElement); // Remove explicit marker if present try { const walker3 = document.createTreeWalker( afterElement, NodeFilter.SHOW_TEXT, null ); const nodes3 = []; while (walker3.nextNode()) nodes3.push(walker3.currentNode); nodes3.forEach((n) => { const orig = n.textContent || ""; const replaced = orig.replace(//gi, ""); if (replaced !== orig) n.textContent = replaced; }); } catch (_) {} // Strip raw social link lines and "Share on ..." prefixes from visible text try { const walker = document.createTreeWalker( afterElement, NodeFilter.SHOW_TEXT, null ); const nodes = []; while (walker.nextNode()) nodes.push(walker.currentNode); const urlRe = /https?:\/\/(x\.com\/medanta|www\.instagram\.com\/medanta|www\.facebook\.com\/medanta)[^\s]*/gi; const sharePrefixRe = /\bShare on (X|Instagram|Facebook)\s*[–-]?\s*/gi; nodes.forEach((n) => { const orig = n.textContent || ""; let txt = orig.replace(urlRe, ""); txt = txt.replace(sharePrefixRe, ""); // Collapse leftover dashes and extra spaces txt = txt .replace(/[–-]+/g, " ") .replace(/\s{2,}/g, " ") .trimStart(); if (txt !== orig) n.textContent = txt; }); // Hide elements that became empty after stripping const blocks = afterElement.querySelectorAll("p, div, li"); blocks.forEach((el) => { if (el.style.display === "none") return; if ((el.textContent || "").trim().length === 0) el.style.display = "none"; }); } catch (_) {} // If only the phrase is present (no URLs and no marker), add icons as a dedicated bubble once if ( !/(https?:\/\/)?x\.com\/medanta/i.test(botText) && !/(https?:\/\/)?(www\.)?instagram\.com\/medanta/i.test(botText) && !/(https?:\/\/)?(www\.)?facebook\.com\/medanta/i.test(botText) && !//i.test(botText) && /We are so glad you took this step towards early detection/i.test( botText ) ) { renderSocialShareOptions(afterElement); } } // Suppress option rendering during progressive FAQ flow (but keep icons injected above) if (typeof medantaFaqInProgress !== "undefined" && medantaFaqInProgress) { return; } // Hide text from the bot bubble if present if (afterElement && //i.test(botText)) { const walker = document.createTreeWalker( afterElement, NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT, null ); while (walker.nextNode()) { const node = walker.currentNode; const text = node.textContent || ""; if (//i.test(text)) { // If it's a text node, hide the parent element if (node.nodeType === Node.TEXT_NODE && node.parentElement) { const parent = node.parentElement; // Only hide if it's a paragraph or contains only this text if ( parent.tagName === "P" || parent.textContent.trim() === "" ) { parent.style.display = "none"; } else { // Replace just the text node content node.textContent = node.textContent.replace( //gi, "" ); } } else if (node.nodeType === Node.ELEMENT_NODE) { // If it's an element, hide it if it contains only this text if (node.textContent.trim() === "") { node.style.display = "none"; } } } } } // Also hide just the explicit "Would you like to see more/some FAQs?" text (not the whole paragraph) if ( afterElement && /Would you like to see (more|some) FAQs\?/i.test(botText) ) { const walker2 = document.createTreeWalker( afterElement, NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT, null ); while (walker2.nextNode()) { const node = walker2.currentNode; if (node.nodeType === Node.TEXT_NODE) { const orig = node.textContent || ""; const replaced = orig.replace( /\bWould you like to see (more|some) FAQs\?\b/gi, "" ); if (replaced !== orig) node.textContent = replaced; } else if (node.nodeType === Node.ELEMENT_NODE) { // If an element contains ONLY that sentence, then hide it; otherwise keep element const onlyFaqCta = /^\s*Would you like to see (more|some) FAQs\?\s*$/i.test( node.textContent || "" ); if (onlyFaqCta) node.style.display = "none"; } } } const lines = botText.split(/\r?\n/); const options = []; const lettered = /^\s*\(([a-eA-E])\)\s+(.+)$/; const bulleted = /^\s*[-*]\s+(.+)$/; const bracketed = /^\s*<([^>]+)>\s*$/; // Medanta: strip textual option lines from within the bot bubble content // We only remove lines that are EXACT options, preserving surrounding question text if (afterElement) { const tw = document.createTreeWalker( afterElement, NodeFilter.SHOW_TEXT, null ); const elementsPossiblyEmpty = new Set(); while (tw.nextNode()) { const textNode = tw.currentNode; const original = textNode.textContent || ""; if (!original.trim()) continue; const linesInNode = original.split(/\r?\n/); let changed = false; const kept = []; for (const ln of linesInNode) { const trimmedLn = ln.trim(); if ( lettered.test(trimmedLn) || bulleted.test(trimmedLn) || bracketed.test(trimmedLn) ) { changed = true; continue; // drop option line } kept.push(ln); } if (changed) { // Collapse leading/trailing blank lines and join while (kept.length && kept[0].trim() === "") kept.shift(); while (kept.length && kept[kept.length - 1].trim() === "") kept.pop(); const newText = kept.join("\n"); textNode.textContent = newText; if (textNode.parentElement) elementsPossiblyEmpty.add(textNode.parentElement); } } // Hide now-empty paragraphs/divs created by stripping elementsPossiblyEmpty.forEach((el) => { if (!el) return; const onlyWhitespace = (el.textContent || "").trim().length === 0; if (onlyWhitespace) { el.style.display = "none"; } }); // Remove redundant
elements that create extra vertical gaps const brs = afterElement.querySelectorAll("br"); brs.forEach((br) => { const prev = br.previousSibling; const next = br.nextSibling; const prevIsGap = !prev || (prev.nodeType === Node.TEXT_NODE && (prev.textContent || "").trim() === "") || (prev.nodeType === Node.ELEMENT_NODE && prev.tagName === "BR"); const nextIsGap = !next || (next.nodeType === Node.TEXT_NODE && (next.textContent || "").trim() === "") || (next.nodeType === Node.ELEMENT_NODE && next.tagName === "BR"); if (prevIsGap && nextIsGap) { br.remove(); } }); // Trim extra top/bottom whitespace by removing margins on first/last visible block const visibleBlocks = Array.from( afterElement.querySelectorAll("p, div") ).filter( (el) => el && el.style.display !== "none" && (el.textContent || "").trim().length > 0 ); if (visibleBlocks.length > 0) { const first = visibleBlocks[0]; const last = visibleBlocks[visibleBlocks.length - 1]; first.style.marginTop = "0"; last.style.marginBottom = "0"; } } for (const line of lines) { let m = line.match(lettered); if (m) { options.push(m[2]); continue; } m = line.match(bulleted); if (m) { // ignore extremely long bullets that are paragraphs, keep short choices if (m[1].length <= 60) options.push(m[1]); } if (!m) { const mb = line.match(bracketed); if (mb) { const text = mb[1]; if (text.length <= 60) options.push(text); } } } // Suppress FAQ CTA buttons coming from LLM text (handled by UI flow elsewhere) // Deduplicate while preserving order const seen = new Set(); let uniqueOptions = options.filter((o) => { const key = o.trim().toLowerCase(); if (seen.has(key)) return false; seen.add(key); return true; }); // Medanta: never render "Show more FAQs"; prefer the explicit first FAQ question button if (isMedantaClient) { uniqueOptions = uniqueOptions.filter( (o) => o.trim().toLowerCase() !== "show more faqs" ); } if (uniqueOptions.length === 0) return; // nothing to render // Inline container directly beneath the bot bubble const container = document.createElement("div"); container.style.display = "flex"; container.style.flexWrap = "wrap"; container.style.gap = "8px"; container.style.alignSelf = "flex-start"; container.style.maxWidth = "80%"; container.style.marginTop = "12px"; container.style.background = "transparent"; container.style.padding = "0"; container.style.boxShadow = "none"; for (const opt of uniqueOptions) { const normalized = opt.trim().toLowerCase(); if (isMedantaClient) { // Override certain buttons to avoid sendMessage and keep UI-only flow if (normalized === "show more faqs") { container.appendChild( createOptionButton(opt, () => { renderFaqProgress(afterElement || container, 0); }) ); continue; } if (normalized === "so, what should i look out for?") { container.appendChild( createOptionButton(opt, () => { renderFaqProgress(afterElement || container, 0); }) ); continue; } if (normalized === "share on social media") { container.appendChild( createOptionButton(opt, () => { // Render icons once beneath this bubble, then offer FAQ entry const shareEl = renderSocialShareOptions( afterElement || container ); const anchor = shareEl || afterElement || container; // After share from generic buttons, show just the FAQ entry button const btnWrap = document.createElement("div"); btnWrap.style.display = "flex"; btnWrap.style.gap = "10px"; btnWrap.style.flexWrap = "wrap"; btnWrap.style.alignSelf = "flex-start"; btnWrap.style.maxWidth = "80%"; btnWrap.style.marginTop = "8px"; const faqBtn = document.createElement("button"); faqBtn.textContent = "So, what should I look out for?"; faqBtn.style.border = "none"; faqBtn.style.borderRadius = "9999px"; faqBtn.style.padding = "10px 16px"; faqBtn.style.fontSize = "1.02em"; faqBtn.style.fontWeight = "600"; faqBtn.style.cursor = "pointer"; faqBtn.style.backgroundColor = isMedantaClient ? "#1e5a87" : "#ef4e36"; faqBtn.style.color = "#ffffff"; faqBtn.style.boxShadow = "0 2px 6px rgba(0,0,0,0.08)"; faqBtn.addEventListener("click", (e) => { e.stopPropagation(); btnWrap.remove(); renderFaqProgress(anchor, 0); }); btnWrap.appendChild(faqBtn); if (anchor && anchor.parentNode) { anchor.parentNode.insertBefore(btnWrap, anchor.nextSibling); } else { chatMessagesListContainer.appendChild(btnWrap); } lastInlineOptionsContainer = btnWrap; scrollToBottom(); }) ); continue; } } container.appendChild(createOptionButton(opt)); } if (afterElement && afterElement.parentNode) { afterElement.parentNode.insertBefore( container, afterElement.nextSibling ); } else { chatMessagesListContainer.appendChild(container); } lastInlineOptionsContainer = container; scrollToBottom(); } function renderAssessmentActionButtons(afterElement) { if (!afterElement || !afterElement.parentNode) return; clearOptionButtons(); const container = document.createElement("div"); container.style.display = "flex"; container.style.gap = "10px"; container.style.flexWrap = "wrap"; container.style.alignSelf = "flex-start"; container.style.maxWidth = "80%"; container.style.marginTop = "8px"; function createActionBtn(label, isPrimary = true) { const btn = document.createElement("button"); btn.textContent = label; btn.style.border = "none"; btn.style.borderRadius = "9999px"; btn.style.padding = "8px 16px"; btn.style.fontSize = "1.02em"; btn.style.fontWeight = "600"; btn.style.cursor = "pointer"; btn.style.backgroundColor = isPrimary ? isMedantaClient ? "#1e5a87" : "#ef4e36" : "#6b7280"; btn.style.color = "#ffffff"; btn.style.boxShadow = "0 2px 6px rgba(0,0,0,0.08)"; return btn; } const showFaqsBtn = createActionBtn("Show more FAQs", true); const thanksBtn = createActionBtn("Thanks for the assessment", true); showFaqsBtn.addEventListener("click", (e) => { e.stopPropagation(); container.remove(); showFaqs(afterElement); }); thanksBtn.addEventListener("click", (e) => { e.stopPropagation(); container.remove(); // Show email question directly const emailQuestionBubble = createBubble( marked.parse( "**Would you like us to send you more information about prostate health?**" ), "bot" ); chatMessagesListContainer.appendChild(emailQuestionBubble); scrollToBottom(); renderMailYesNoButtons(emailQuestionBubble); }); container.appendChild(showFaqsBtn); container.appendChild(thanksBtn); afterElement.parentNode.insertBefore(container, afterElement.nextSibling); lastInlineOptionsContainer = container; scrollToBottom(); } // showFaqs removed (progressive FAQ flow is used) function renderMailYesNoButtons(afterElement) { if (!afterElement || !afterElement.parentNode) return; const container = document.createElement("div"); container.style.display = "flex"; container.style.gap = "10px"; container.style.flexWrap = "wrap"; container.style.alignSelf = "flex-start"; container.style.maxWidth = "80%"; container.style.marginTop = "8px"; function createPrimaryBtn(label, value) { const btn = document.createElement("button"); btn.textContent = label; btn.style.border = "none"; btn.style.borderRadius = "9999px"; btn.style.padding = "8px 16px"; btn.style.fontWeight = "600"; btn.style.cursor = "pointer"; btn.style.backgroundColor = isMedantaClient ? "#1e5a87" : "#ef4e36"; btn.style.color = "#ffffff"; btn.style.boxShadow = "0 2px 6px rgba(0,0,0,0.08)"; btn.addEventListener("click", (e) => { e.stopPropagation(); // Remove the Yes/No container to avoid layout shifts if (container && container.parentNode) { container.parentNode.removeChild(container); } // Show user's choice as a user bubble directly after the question const userReplyBubble = createBubble(label, "user"); afterElement.parentNode.insertBefore( userReplyBubble, afterElement.nextSibling ); scrollToBottom(); if (value === "yes") { // Render inline contact form instead of sending immediately renderContactForm(userReplyBubble); } else { // No, thank you: show social icons and end const shareEl = renderSocialShareOptions(userReplyBubble); const anchor = shareEl || userReplyBubble; const thanks = createBubble( marked.parse("Thank you for taking this assessment."), "bot" ); anchor.parentNode.insertBefore(thanks, anchor.nextSibling); scrollToBottom(); } }); return btn; } container.appendChild(createPrimaryBtn("Yes, please.", "yes")); // New: Share on social button (custom, no user bubble) const shareBtn = document.createElement("button"); shareBtn.textContent = "Share on social media"; shareBtn.style.border = "none"; shareBtn.style.borderRadius = "9999px"; shareBtn.style.padding = "8px 16px"; shareBtn.style.fontSize = "1.02em"; shareBtn.style.fontWeight = "600"; shareBtn.style.cursor = "pointer"; shareBtn.style.backgroundColor = isMedantaClient ? "#1e5a87" : "#ef4e36"; shareBtn.style.color = "#ffffff"; shareBtn.style.boxShadow = "0 2px 6px rgba(0,0,0,0.08)"; shareBtn.addEventListener("click", (e) => { e.stopPropagation(); container.remove(); const shareEl = renderSocialShareOptions(afterElement); const anchor = shareEl || afterElement; const thanks = createBubble( marked.parse("Thank you for taking this assessment."), "bot" ); if (anchor && anchor.parentNode) { anchor.parentNode.insertBefore(thanks, anchor.nextSibling); } else { chatMessagesListContainer.appendChild(thanks); } scrollToBottom(); }); container.appendChild(shareBtn); // New: First FAQ question as a custom button (no user bubble) const faqBtn = document.createElement("button"); faqBtn.textContent = "So, what should I look out for?"; faqBtn.style.border = "none"; faqBtn.style.borderRadius = "9999px"; faqBtn.style.padding = "10px 16px"; faqBtn.style.fontSize = "1.02em"; faqBtn.style.fontWeight = "600"; faqBtn.style.cursor = "pointer"; faqBtn.style.backgroundColor = isMedantaClient ? "#1e5a87" : "#ef4e36"; faqBtn.style.color = "#ffffff"; faqBtn.style.boxShadow = "0 2px 6px rgba(0,0,0,0.08)"; faqBtn.addEventListener("click", (e) => { e.stopPropagation(); if (e.stopImmediatePropagation) e.stopImmediatePropagation(); container.remove(); renderFaqProgress(afterElement, 0); }); container.appendChild(faqBtn); afterElement.parentNode.insertBefore(container, afterElement.nextSibling); lastInlineOptionsContainer = container; scrollToBottom(); } function renderContactForm(afterElement) { if (!afterElement || !afterElement.parentNode) return; clearOptionButtons(); let nameValue = ""; let mobileValue = ""; function makeInput(placeholder) { const input = document.createElement("input"); input.type = "text"; input.placeholder = placeholder; input.style.width = "100%"; input.style.border = "none"; input.style.borderRadius = "10px"; input.style.padding = "12px 14px"; input.style.fontSize = "14px"; input.style.outline = "none"; if (isMedantaClient) input.style.backgroundColor = "#1e5a87"; input.style.color = "#ffffff"; input.style.caretColor = "#ffffff"; input.style.boxSizing = "border-box"; input.className = "medanta-input"; input.addEventListener("focus", () => { input.style.boxShadow = "0 0 0 3px rgba(239,78,54,0.25)"; }); input.addEventListener("blur", () => { input.style.boxShadow = "none"; }); if (window.innerWidth <= 380) { input.style.fontSize = "12px"; input.style.padding = "10px 12px"; } return input; } function extractDigits(value) { return (value || "").replace(/\D+/g, ""); } function isValidPhoneNumber(value) { const digits = extractDigits(value); return digits.length === 10; } function flashError(element, messageEl, defaultText) { if (messageEl) { const original = messageEl.textContent; messageEl.textContent = defaultText || original; } element.style.boxShadow = "0 0 0 3px rgba(239,78,54,0.5)"; element.style.transition = "box-shadow 0.2s ease"; setTimeout(() => { element.style.boxShadow = "none"; }, 1500); } function makeSubmitButton(text) { const submit = document.createElement("button"); submit.textContent = text; submit.style.border = "none"; submit.style.borderRadius = "9999px"; submit.style.padding = "10px 16px"; submit.style.fontWeight = "600"; submit.style.cursor = "pointer"; submit.style.backgroundColor = "#1e5a87"; submit.style.color = "#ffffff"; submit.style.boxShadow = "0 2px 6px rgba(0,0,0,0.08)"; submit.style.width = "100%"; submit.addEventListener("mouseenter", () => { submit.style.opacity = "0.9"; }); submit.addEventListener("mouseleave", () => { submit.style.opacity = "1"; }); return submit; } function renderNameStep() { const nameFormWrap = document.createElement("div"); nameFormWrap.style.display = "flex"; nameFormWrap.style.flexDirection = "column"; nameFormWrap.style.gap = "8px"; nameFormWrap.style.alignSelf = "flex-start"; nameFormWrap.style.maxWidth = "100%"; nameFormWrap.style.marginTop = "8px"; const nameInput = makeInput("Your name"); const nameSubmit = makeSubmitButton("Submit"); nameFormWrap.appendChild(nameInput); nameFormWrap.appendChild(nameSubmit); afterElement.parentNode.insertBefore( nameFormWrap, afterElement.nextSibling ); lastInlineOptionsContainer = nameFormWrap; scrollToBottom(); nameSubmit.addEventListener("click", (e) => { e.stopPropagation(); const value = nameInput.value.trim(); if (!value) { nameSubmit.textContent = "Please enter your name"; setTimeout(() => (nameSubmit.textContent = "Submit"), 1500); return; } nameValue = value; const nameBubble = createBubble(`Name: ${nameValue}`, "user"); nameFormWrap.parentNode.insertBefore( nameBubble, nameFormWrap.nextSibling ); nameFormWrap.remove(); scrollToBottom(); setTimeout(() => renderMobileStep(nameBubble), 250); }); } function renderMobileStep(anchorBubble) { const mobileFormWrap = document.createElement("div"); mobileFormWrap.style.display = "flex"; mobileFormWrap.style.flexDirection = "column"; mobileFormWrap.style.gap = "8px"; mobileFormWrap.style.alignSelf = "flex-start"; mobileFormWrap.style.maxWidth = "100%"; mobileFormWrap.style.marginTop = "8px"; const mobileInput = makeInput("Mobile number"); const mobileSubmit = makeSubmitButton("Submit"); mobileFormWrap.appendChild(mobileInput); mobileFormWrap.appendChild(mobileSubmit); anchorBubble.parentNode.insertBefore( mobileFormWrap, anchorBubble.nextSibling ); lastInlineOptionsContainer = mobileFormWrap; scrollToBottom(); mobileSubmit.addEventListener("click", (e) => { e.stopPropagation(); const value = mobileInput.value.trim(); if (!value) { mobileSubmit.textContent = "Please enter your mobile number"; setTimeout(() => (mobileSubmit.textContent = "Submit"), 1500); flashError( mobileInput, mobileSubmit, "Please enter your mobile number" ); return; } if (isMedantaClient && !isValidPhoneNumber(value)) { mobileSubmit.textContent = "Enter a valid 10 digit number"; flashError( mobileInput, mobileSubmit, "Enter a valid 10 digit number" ); setTimeout(() => (mobileSubmit.textContent = "Submit"), 1500); return; } mobileValue = value; const mobileBubble = createBubble(`Mobile: ${mobileValue}`, "user"); mobileFormWrap.parentNode.insertBefore( mobileBubble, mobileFormWrap.nextSibling ); mobileFormWrap.remove(); scrollToBottom(); setTimeout(() => { const payload = `**Your details**\nName: ${nameValue}\nMobile: ${mobileValue}`; logMedantaAnalyticsEvent("contact_submitted", { name: nameValue, mobile: mobileValue, }); sendMessage(payload); startOrResetInactivityTimer(); }, 300); }); } renderNameStep(); } // renderShareOrFaqChoice removed (no longer used) // Helper: Render Yes + No Thank you under an email question bubble function renderEmailYesNoOnly(emailQuestionBubble) { const container = document.createElement("div"); container.style.display = "flex"; container.style.gap = "10px"; container.style.flexWrap = "wrap"; container.style.alignSelf = "flex-start"; container.style.maxWidth = "80%"; container.style.marginTop = "8px"; const yesBtn = document.createElement("button"); yesBtn.textContent = "Yes, please."; yesBtn.style.border = "none"; yesBtn.style.borderRadius = "9999px"; yesBtn.style.padding = "8px 16px"; yesBtn.style.fontWeight = "600"; yesBtn.style.cursor = "pointer"; yesBtn.style.backgroundColor = isMedantaClient ? "#1e5a87" : "#ef4e36"; yesBtn.style.color = "#ffffff"; yesBtn.style.boxShadow = "0 2px 6px rgba(0,0,0,0.08)"; yesBtn.addEventListener("click", (e) => { e.stopPropagation(); container.remove(); const userReplyBubble = createBubble("Yes, please.", "user"); emailQuestionBubble.parentNode.insertBefore( userReplyBubble, emailQuestionBubble.nextSibling ); renderContactForm(userReplyBubble); }); const noBtn = document.createElement("button"); noBtn.textContent = "No, thank you."; noBtn.style.border = "none"; noBtn.style.borderRadius = "9999px"; noBtn.style.padding = "8px 16px"; noBtn.style.fontWeight = "600"; noBtn.style.cursor = "pointer"; noBtn.style.backgroundColor = "#6b7280"; noBtn.style.color = "#ffffff"; noBtn.style.boxShadow = "0 2px 6px rgba(0,0,0,0.08)"; noBtn.addEventListener("click", (e) => { e.stopPropagation(); container.remove(); const thanks = createBubble( marked.parse("Thank you for taking this assessment."), "bot" ); emailQuestionBubble.parentNode.insertBefore( thanks, emailQuestionBubble.nextSibling ); scrollToBottom(); }); container.appendChild(yesBtn); container.appendChild(noBtn); emailQuestionBubble.parentNode.insertBefore( container, emailQuestionBubble.nextSibling ); lastInlineOptionsContainer = container; scrollToBottom(); } // Render just the first FAQ button (Q1) below a given anchor function renderFaqFirstButton(anchorElement) { const container = document.createElement("div"); container.style.display = "flex"; container.style.gap = "10px"; container.style.flexWrap = "wrap"; container.style.alignSelf = "flex-start"; container.style.maxWidth = "80%"; container.style.marginTop = "8px"; const btn = document.createElement("button"); btn.textContent = "So, what should I look out for?"; btn.style.border = "none"; btn.style.borderRadius = "9999px"; btn.style.padding = "10px 16px"; btn.style.fontSize = "1.02em"; btn.style.fontWeight = "600"; btn.style.cursor = "pointer"; btn.style.backgroundColor = isMedantaClient ? "#1e5a87" : "#ef4e36"; btn.style.color = "#ffffff"; btn.style.boxShadow = "0 2px 6px rgba(0,0,0,0.08)"; btn.addEventListener("click", (e) => { e.stopPropagation(); container.remove(); renderFaqProgress(anchorElement, 0); }); container.appendChild(btn); if (anchorElement && anchorElement.parentNode) { anchorElement.parentNode.insertBefore( container, anchorElement.nextSibling ); } else { chatMessagesListContainer.appendChild(container); } lastInlineOptionsContainer = container; scrollToBottom(); } // Render Email + FAQ buttons below an anchor // renderEmailAndFaqButtons removed (no longer used) // Progressive FAQ flow starting from an index; after last, ask email again and show share function renderFaqProgress(anchorElement, startIndex) { medantaFaqInProgress = true; clearOptionButtons(); const faqBlocks = [ { q: "**So, what should I look out for?**", a: "Prostate cancer usually develops without any symptoms. Sometimes, pain or changes in urination (irregular flow, difficulty starting or stopping urination, frequent urination or dribbling) may point to prostate cancer, but these can also be symptoms of other conditions. Talking to a urologist is the best way forward.", }, { q: "**How is prostate cancer detected?**", a: "Initial screening for prostate cancer involves PSA (Prostate-Specific Antigen) testing. Your urologist may also recommend Digital Rectal Examination (DRE) and an MRI Scan for further investigation. However, a biopsy is needed to confirm the findings.", }, { q: "**What is the PSA test?**", a: "The PSA test is a simple blood test that shows the levels of Prostate-Specific Antigen in your blood. It is a safe and effective way to assess your prostate health. It not only helps detect cancer, but other prostate conditions as well. Your urologist will help you decide whether you need PSA, depending on your age and other risk factors.", }, { q: "**Tell me more.**", a: "While the PSA test gives a fair idea about prostate health, it is important to remember that elevated PSA levels do not necessarily indicate prostate cancer. It could also point towards an enlarged prostate or infection. \n\nYour doctor may also advise DRE and MRI if you have risk factors or symptoms. Regular tests can spot trends in PSA blood levels, which could be a sign of prostate cancer developing, particularly if you have an increased risk.", }, { q: "**What should I do next?**", a: "We strongly recommend that you talk to an experienced urologist about the pros and cons of getting a PSA test, and whether it is right for you. You could also read more about prostate health here.", }, ]; let idx = Math.max(0, startIndex || 0); const showStep = (prevEl) => { if (idx >= faqBlocks.length) { // After FAQs, ask email again and show share medantaFaqInProgress = false; const emailQuestionBubble = createBubble( marked.parse( "**Would you like us to send you more information about prostate health?**" ), "bot" ); if (prevEl && prevEl.parentNode) { prevEl.parentNode.insertBefore( emailQuestionBubble, prevEl.nextSibling ); } else { chatMessagesListContainer.appendChild(emailQuestionBubble); } scrollToBottom(); // Show Yes + No Thank you buttons const container = document.createElement("div"); container.style.display = "flex"; container.style.gap = "10px"; container.style.flexWrap = "wrap"; container.style.alignSelf = "flex-start"; container.style.maxWidth = "80%"; container.style.marginTop = "8px"; const yesBtn = document.createElement("button"); yesBtn.textContent = "Yes, please."; yesBtn.style.border = "none"; yesBtn.style.borderRadius = "9999px"; yesBtn.style.padding = "8px 16px"; yesBtn.style.fontSize = "1.02em"; yesBtn.style.fontWeight = "600"; yesBtn.style.cursor = "pointer"; yesBtn.style.backgroundColor = isMedantaClient ? "#1e5a87" : "#ef4e36"; yesBtn.style.color = "#ffffff"; yesBtn.style.boxShadow = "0 2px 6px rgba(0,0,0,0.08)"; yesBtn.addEventListener("click", (e) => { e.stopPropagation(); container.remove(); const userReplyBubble = createBubble("Yes, please.", "user"); emailQuestionBubble.parentNode.insertBefore( userReplyBubble, emailQuestionBubble.nextSibling ); renderContactForm(userReplyBubble); }); const noBtn = document.createElement("button"); noBtn.textContent = "No, thank you."; noBtn.style.border = "none"; noBtn.style.borderRadius = "9999px"; noBtn.style.padding = "8px 16px"; noBtn.style.fontSize = "1.02em"; noBtn.style.fontWeight = "600"; noBtn.style.cursor = "pointer"; noBtn.style.backgroundColor = "#6b7280"; noBtn.style.color = "#ffffff"; noBtn.style.boxShadow = "0 2px 6px rgba(0,0,0,0.08)"; noBtn.addEventListener("click", (e) => { e.stopPropagation(); container.remove(); // Show social icons and end const shareEl = renderSocialShareOptions(emailQuestionBubble); const thanks = createBubble( marked.parse("Thank you for taking this assessment."), "bot" ); const anchor = shareEl || emailQuestionBubble; if (anchor && anchor.parentNode) { anchor.parentNode.insertBefore(thanks, anchor.nextSibling); } else { chatMessagesListContainer.appendChild(thanks); } scrollToBottom(); }); container.appendChild(yesBtn); container.appendChild(noBtn); chatMessagesListContainer.appendChild(container); lastInlineOptionsContainer = container; scrollToBottom(); return; } const currentIdx = idx; const { q, a } = faqBlocks[currentIdx]; // Track FAQ view const faqName = q .replace(/^\*\*\s*/, "") .replace(/\*\*$/, "") .trim(); logMedantaAnalyticsEvent("faq_viewed", { faq_name: faqName, faq_index: currentIdx, }); const qBubble = createBubble(marked.parse(q), "bot"); // Prefer placing the next question after the previous answer if available if (prevEl && prevEl.parentNode) { prevEl.parentNode.insertBefore(qBubble, prevEl.nextSibling); } else if (anchorElement && anchorElement.parentNode) { anchorElement.parentNode.insertBefore( qBubble, anchorElement.nextSibling ); } else { chatMessagesListContainer.appendChild(qBubble); } scrollToBottom(); const aBubble = createBubble("", "bot"); qBubble.parentNode.insertBefore(aBubble, qBubble.nextSibling); scrollToBottom(); const answerHtml = marked.parse(a); typeHtmlWordByWord(aBubble, answerHtml, { wordDelay: isMedantaClient ? 300 : 120, wordsPerStep: isMedantaClient ? 2 : 3, onProgress: scrollToBottom, onComplete: () => { scrollToBottom(); const nextIndex = currentIdx + 1; idx = nextIndex; if (nextIndex < faqBlocks.length) { clearOptionButtons(); const nextBtnWrap = document.createElement("div"); nextBtnWrap.style.display = "flex"; nextBtnWrap.style.gap = "10px"; nextBtnWrap.style.flexWrap = "wrap"; nextBtnWrap.style.alignSelf = "flex-start"; nextBtnWrap.style.maxWidth = "80%"; nextBtnWrap.style.marginTop = "8px"; const nextBtn = document.createElement("button"); nextBtn.textContent = faqBlocks[nextIndex].q .replace(/^\*\*\s*/, "") .replace(/\*\*$/, ""); nextBtn.style.border = "none"; nextBtn.style.borderRadius = "9999px"; nextBtn.style.padding = "10px 16px"; nextBtn.style.fontSize = "1.02em"; nextBtn.style.fontWeight = "600"; nextBtn.style.cursor = "pointer"; nextBtn.style.backgroundColor = isMedantaClient ? "#1e5a87" : "#ef4e36"; nextBtn.style.color = "#ffffff"; nextBtn.style.boxShadow = "0 2px 6px rgba(0,0,0,0.08)"; nextBtn.addEventListener("click", (e) => { e.stopPropagation(); nextBtnWrap.remove(); showStep(aBubble); }); aBubble.parentNode.insertBefore(nextBtnWrap, aBubble.nextSibling); nextBtnWrap.appendChild(nextBtn); lastInlineOptionsContainer = nextBtnWrap; scrollToBottom(); } else { showStep(aBubble); } }, }); }; showStep(anchorElement); // When FAQ flow completes in showStep, medantaFaqInProgress will be reset by email/share branch or elsewhere. } // renderSingleFaqsButton removed (no longer used) function renderSocialShareOptions(afterElement) { const shareTargetUrl = window.location.href.split("#")[0]; const encodedShareUrl = encodeURIComponent(shareTargetUrl); const shareMessage = "I just found out my prostate cancer risk level with Medanta’s 1-Minute Prostate Check. Try it out and share it with all the men you care about."; const encodedShareMessage = encodeURIComponent(shareMessage); const whatsappMessage = `${shareMessage} ${shareTargetUrl}`; const encodedWhatsappMessage = encodeURIComponent(whatsappMessage); const shareHtml = `
We are so glad you took this step towards early detection and a healthier tomorrow. Share this and inspire others to join the movement.
#LetsFightCancerTogether
`; const shareBubble = createBubble(shareHtml, "bot"); if (afterElement && afterElement.parentNode) { afterElement.parentNode.insertBefore( shareBubble, afterElement.nextSibling ); } else { chatMessagesListContainer.appendChild(shareBubble); } // Track social sharing clicks setTimeout(() => { const socialLinks = shareBubble.querySelectorAll( "a[aria-label^='Share on']" ); socialLinks.forEach((link) => { link.addEventListener("click", () => { const platform = link .getAttribute("aria-label") .replace("Share on ", "") .toLowerCase(); logMedantaAnalyticsEvent("social_shared", { platform: platform }); }); }); }, 100); scrollToBottom(); return shareBubble; } function injectSocialIconsIntoBubble(bubbleEl) { try { if (!bubbleEl) return; let insertBeforeNode = null; // First, hide all tags that link to social media const links = bubbleEl.querySelectorAll("a"); links.forEach((link) => { const href = link.getAttribute("href") || ""; if ( /(x\.com\/medanta|instagram\.com\/medanta|facebook\.com\/medanta|linkedin\.com\/.*medanta|wa\.me|whatsapp\.com)/i.test( href ) ) { if (!insertBeforeNode) insertBeforeNode = link.parentElement || link; link.style.display = "none"; } }); // Process paragraphs - hide social link-only lines and FAQ CTA line const paragraphs = bubbleEl.querySelectorAll("p"); paragraphs.forEach((p) => { // Skip if already hidden if (p.style.display === "none") return; const text = p.textContent || ""; const trimmed = text.trim(); // Hide only the FAQ CTA sentence text if present (keep other content) if (/Would you like to see (more|some) FAQs\?/i.test(trimmed)) { // If the paragraph is only that sentence, hide; else strip the text from text nodes if ( /^\s*Would you like to see (more|some) FAQs\?\s*$/i.test(trimmed) ) { if (!insertBeforeNode) insertBeforeNode = p; p.style.display = "none"; return; } else { const textNodes = []; const walker = document.createTreeWalker( p, NodeFilter.SHOW_TEXT, null ); while (walker.nextNode()) textNodes.push(walker.currentNode); textNodes.forEach((n) => { const orig = n.textContent || ""; const replaced = orig.replace( /\bWould you like to see (more|some) FAQs\?\b/gi, "" ); if (replaced !== orig) n.textContent = replaced; }); } } // Check if this paragraph contains social link URLs or "Share on" prefix const hasSocialLink = /(x\.com\/medanta|instagram\.com\/medanta|facebook\.com\/medanta)/i.test( trimmed ); const isSharePrefix = /^Share on (X|Instagram|Facebook)\s*[–-]?\s*/i.test(trimmed); if (hasSocialLink || isSharePrefix) { // Check if the paragraph ONLY contains social link content (no other meaningful text) // Match patterns like "Share on X – https://x.com/medanta" or just URLs const isOnlySocialLink = /^(Share on (X|Instagram|Facebook)\s*[–-]?\s*)?(https?:\/\/)?(x\.com\/medanta|www\.instagram\.com\/medanta|www\.facebook\.com\/medanta)[^\s]*\s*$/i.test( trimmed ); // Only hide if paragraph is EXCLUSIVELY a social link (matches the pattern exactly) // This ensures we keep paragraphs like "We are so glad..." which won't match this pattern if (isOnlySocialLink) { if (!insertBeforeNode) insertBeforeNode = p; p.style.display = "none"; } else { // Paragraph has other content - just remove social link text from text nodes // but keep the paragraph visible const textNodes = []; const walker = document.createTreeWalker( p, NodeFilter.SHOW_TEXT, null ); while (walker.nextNode()) { textNodes.push(walker.currentNode); } textNodes.forEach((textNode) => { let nodeText = textNode.textContent || ""; const original = nodeText; nodeText = nodeText .replace(/Share on (X|Instagram|Facebook)\s*[–-]?\s*/gi, "") .replace( /https?:\/\/(x\.com\/medanta|www\.instagram\.com\/medanta|www\.facebook\.com\/medanta)[^\s]*/gi, "" ) .trim(); if (nodeText !== original) { textNode.textContent = nodeText; } }); } } }); // Build icon row (same visuals as renderSocialShareOptions) const wrapper = document.createElement("div"); wrapper.style.display = "flex"; wrapper.style.gap = "16px"; wrapper.style.alignItems = "center"; wrapper.style.flexWrap = "wrap"; wrapper.style.marginTop = "12px"; wrapper.style.marginBottom = "14px"; const mkLink = (href, bg, svgPath, svgExtra = "") => { const a = document.createElement("a"); a.href = href; a.target = "_blank"; a.rel = "noopener"; a.style.display = "inline-flex"; a.style.alignItems = "center"; a.style.justifyContent = "center"; a.style.width = "40px"; a.style.height = "40px"; a.style.borderRadius = bg.includes("gradient") ? "12px" : "9999px"; a.style.background = bg; a.style.color = "#fff"; a.style.textDecoration = "none"; const svg = document.createElementNS( "http://www.w3.org/2000/svg", "svg" ); svg.setAttribute("viewBox", "0 0 24 24"); svg.setAttribute("width", "20"); svg.setAttribute("height", "20"); svg.setAttribute("aria-hidden", "true"); svg.setAttribute("fill", "currentColor"); svg.innerHTML = svgExtra || ``; a.appendChild(svg); return a; }; const xLink = mkLink( "https://x.com/medanta", "#000", "M18.244 2H21l-6.56 7.497L22.5 22h-6.656l-5.2-6.78L4.5 22H2l7.07-8.09L1.5 2h6.78l4.72 6.2L18.244 2Zm-1.16 18h1.812L7.02 4H5.12l11.964 16Z" ); const igSVG = ` `; const igLink = mkLink( "https://www.instagram.com/medanta/?hl=en", "radial-gradient( 120% 120% at 30% 107%, #fdf497 0%, #fd5949 40%, #d6249f 70%, #285AEB 100% )", "", igSVG ); const fbLink = mkLink( "https://www.facebook.com/medanta/", "#1877F2", "M22 12a10 10 0 1 0-11.5 9.9v-7H7.9V12h2.6V9.8c0-2.6 1.55-4.1 3.92-4.1c1.14 0 2.33.2 2.33.2v2.56h-1.31c-1.3 0-1.7.81-1.7 1.64V12h2.89l-.46 2.9h-2.43v7A10 10 0 0 0 22 12Z" ); wrapper.appendChild(xLink); wrapper.appendChild(igLink); wrapper.appendChild(fbLink); if (insertBeforeNode && insertBeforeNode.parentNode) { insertBeforeNode.parentNode.insertBefore(wrapper, insertBeforeNode); } else { bubbleEl.appendChild(wrapper); } } catch (_) { // no-op safeguard } } // showFaqsAfterContact removed (progressive FAQ flow is used) // Removed global click/keystroke resets: timer now only starts/resets on initial load, // user messages, and bot replies (no page-wide activity resets). // Make the collapsed chat clickable to expand chatbotContainer.addEventListener("click", (e) => { // Only expand if clicked on container but not on buttons if ( !centralChatExpanded && !e.target.closest("#centralChatEnlargeButton") && !e.target.closest("#centralChatCloseButton") ) { handleChatWidgetEnlargeOrContract(); } }); cahoAssistantSmallButton.addEventListener("click", () => { cahoAssistantSmallButton.style.setProperty( "display", "none", "important" ); chatbotContainer.style.setProperty("display", "flex", "important"); // Start in collapsed state centralChatExpanded = false; chatMessagesListContainer.style.setProperty( "display", "none", "important" ); contractSvgIcon.classList.add("tulu-hidden"); expandSvgIcon.classList.remove("tulu-hidden"); topLogosContainer.style.marginBottom = "0px"; }); centralChatCloseButton.addEventListener("click", (e) => { e.stopPropagation(); // Prevent container click event // console.log("Close button clicked"); cahoAssistantSmallButton.style.setProperty( "display", "block", "important" ); chatbotContainer.style.setProperty("display", "none", "important"); // Reset state centralChatExpanded = false; chatMessagesListContainer.style.setProperty( "display", "none", "important" ); contractSvgIcon.classList.add("tulu-hidden"); expandSvgIcon.classList.remove("tulu-hidden"); topLogosContainer.style.marginBottom = "0px"; }); function handleChatWidgetEnlargeOrContract() { if (centralChatExpanded) { // action: contract to small widget // console.log("Contracting to small widget"); // show expand icon, hide contract icon contractSvgIcon.classList.add("tulu-hidden"); expandSvgIcon.classList.remove("tulu-hidden"); topLogosContainer.style.marginBottom = "0px"; // hide the messages list container: do not show messages chatMessagesListContainer.style.setProperty( "display", "none", "important" ); // hide suggestions on contract botDescriptionAndSuggestionsContainer.style.setProperty( "display", "none", "important" ); centralChatExpanded = false; // console.log("Contracted, new state:", centralChatExpanded); } else { // action: expand to bigger window // console.log("Expanding to bigger window"); // Normal expansion behavior // show contract icon, hide expand icon contractSvgIcon.classList.remove("tulu-hidden"); expandSvgIcon.classList.add("tulu-hidden"); topLogosContainer.style.marginBottom = isMedantaClient ? "0px" : "20px"; // show the messages list container chatMessagesListContainer.style.setProperty( "display", "flex", "important" ); let targetMinHeight = "60vh"; if (isMedantaClient) { const isMobileViewport = window.matchMedia("(max-width: 767px)").matches; targetMinHeight = isMobileViewport ? "92vh" : "88vh"; } chatMessagesListContainer.style.setProperty( "min-height", targetMinHeight, "important" ); if (isMedantaClient) { chatMessagesListContainer.style.paddingTop = "2px"; chatMessagesListContainer.style.paddingBottom = "12px"; } // show suggestions on expand (skip for Medanta custom layout) if (isMedantaClient) { botDescriptionAndSuggestionsContainer.style.setProperty( "display", "none", "important" ); } else { botDescriptionAndSuggestionsContainer.style.setProperty( "display", "flex", "important" ); } centralChatExpanded = true; // console.log("Expanded, new state:", centralChatExpanded); // Medanta: show hero banner, intro bubble and CTA once if (isMedantaClient && !medantaIntroShown) { // Use requestAnimationFrame to ensure layout has settled before rendering requestAnimationFrame(() => { // Render responsive banner once if (!document.getElementById("medantaHeroBanner")) { const bannerWrap = document.createElement("div"); bannerWrap.style.display = "flex"; bannerWrap.style.justifyContent = "center"; bannerWrap.style.alignItems = "center"; bannerWrap.style.width = "100%"; bannerWrap.style.maxWidth = "640px"; bannerWrap.style.padding = "0 12px"; const isMobileBanner = window.matchMedia("(max-width: 767px)").matches; bannerWrap.style.margin = isMobileBanner ? "34px auto 16px auto" : "5px auto 12px auto"; const picture = document.createElement("picture"); picture.id = "medantaHeroBanner"; picture.style.width = "100%"; picture.style.display = "block"; const desktopSource = document.createElement("source"); desktopSource.srcset = "https://main.tuluhealth.com/static/medanta_desktop_banner.png"; desktopSource.media = "(min-width: 768px)"; const mobileSource = document.createElement("source"); mobileSource.srcset = "https://main.tuluhealth.com/static/medanta_mobile_banner.png"; mobileSource.media = "(max-width: 767px)"; const bannerImg = document.createElement("img"); bannerImg.src = "https://main.tuluhealth.com/static/medanta_mobile_banner.png"; bannerImg.alt = "Medanta Prostate Assessment"; bannerImg.style.width = "100%"; if (isMobileBanner) { bannerImg.style.height = "auto"; bannerImg.style.maxHeight = "220px"; bannerImg.style.objectFit = "contain"; } else { bannerImg.style.height = "auto"; bannerImg.style.maxHeight = "420px"; bannerImg.style.objectFit = "contain"; } bannerImg.style.display = "block"; bannerImg.style.borderRadius = "12px"; picture.appendChild(desktopSource); picture.appendChild(mobileSource); picture.appendChild(bannerImg); bannerWrap.appendChild(picture); chatMessagesListContainer.appendChild(bannerWrap); } // First bot bubble text const introText = "Prostate cancer affects **1 in 8 men** in their lifetime, making it the world’s second most common cancer in men.\n\nHowever, it is completely treatable if caught early. Three quick questions will help you check your risk."; const introBubble = createBubble(marked.parse(introText), "bot"); chatMessagesListContainer.appendChild(introBubble); // Inline CTA button below the intro bubble const ctaContainer = document.createElement("div"); ctaContainer.style.display = "flex"; ctaContainer.style.flexWrap = "wrap"; ctaContainer.style.gap = "8px"; ctaContainer.style.alignSelf = "flex-start"; ctaContainer.style.maxWidth = "80%"; ctaContainer.style.marginTop = "4px"; const ctaBtn = createOptionButton("I’m Ready"); ctaContainer.appendChild(ctaBtn); chatMessagesListContainer.appendChild(ctaContainer); medantaIntroShown = true; // Force scroll after all content is rendered - use multiple timeouts to ensure it works const forceScroll = () => { chatMessagesListContainer.scrollTop = chatMessagesListContainer.scrollHeight; }; forceScroll(); requestAnimationFrame(forceScroll); setTimeout(forceScroll, 50); setTimeout(forceScroll, 150); setTimeout(forceScroll, 300); }); } } } centralChatEnlargeButton.addEventListener("click", (e) => { e.stopPropagation(); // Prevent container click event handleChatWidgetEnlargeOrContract(); }); textInput.addEventListener("focus", (e) => { e.stopPropagation(); // Prevent container click event sendMessageButton.style.backgroundColor = "#6b7cfc"; if (!centralChatExpanded) handleChatWidgetEnlargeOrContract(); const paths = sendMessageButtonSvg.getElementsByTagName("path"); for (let i = 0; i < paths.length; i++) { paths[i].setAttribute("stroke", "white"); } }); const onTextInputOutOfFocus = () => { sendMessageButton.style.backgroundColor = "transparent"; const paths = sendMessageButtonSvg.getElementsByTagName("path"); for (let i = 0; i < paths.length; i++) { paths[i].setAttribute("stroke", "#6b7cfc"); } }; textInput.addEventListener("focusout", onTextInputOutOfFocus); textInput.addEventListener("blur", onTextInputOutOfFocus); // Auto-resize textarea function autoResize() { textInput.style.height = "auto"; textInput.style.height = Math.min(textInput.scrollHeight, 30) + "px"; } textInput.addEventListener("input", autoResize); textInput.addEventListener("keydown", (e) => { if (e.key === "Enter") { // If Shift+Enter allow new line and auto-resize if (e.shiftKey) { setTimeout(autoResize, 0); } } }); sendMessageButton.addEventListener("click", (e) => { e.stopPropagation(); // Prevent container click event sendMessage(textInput.value); startOrResetInactivityTimer(); }); textInput.addEventListener("keydown", (e) => { if (e.key === "Enter") { // Shift+Enter: allow default newline, then resize if (e.shiftKey) { setTimeout(autoResize, 0); return; } // Enter alone: send message e.preventDefault(); sendMessage(textInput.value); startOrResetInactivityTimer(); } }); // Backup keypress listener for better compatibility textInput.addEventListener("keypress", (e) => { if (e.key === "Enter" || e.keyCode === 13) { // Shift+Enter: let default add newline if (e.shiftKey) { setTimeout(autoResize, 0); return; } // Enter alone: send e.preventDefault(); sendMessage(textInput.value); startOrResetInactivityTimer(); } }); function scrollToBottom() { chatMessagesListContainer.scrollTop = chatMessagesListContainer.scrollHeight; } // small utility for timed delays const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); async function sendMessage(questionText, suppressUserBubble = false) { const question = questionText.trim(); if (!question || question.length === 0) return; removeBotDescriptionAndSuggestionsContainer(); chatMessagesListContainer.style.paddingTop = "12px"; chatMessagesListContainer.style.paddingBottom = "12px"; // Clear option buttons before sending a new message (Medanta) clearOptionButtons(); // Track Q1, Q2, Q3 answers sequentially before sending to backend if (isMedantaClient && !suppressUserBubble && medantaQuestionStep > 0) { const trimmedAnswer = question.trim(); if (medantaQuestionStep === 1) { logMedantaAnalyticsEvent("q1_answered", { answer: trimmedAnswer }); medantaQuestionStep = 2; } else if (medantaQuestionStep === 2) { logMedantaAnalyticsEvent("q2_answered", { answer: trimmedAnswer }); medantaQuestionStep = 3; } else if (medantaQuestionStep === 3) { logMedantaAnalyticsEvent("q3_answered", { answer: trimmedAnswer }); medantaQuestionStep = 0; } } if (!suppressUserBubble) { let displayText = question; if (displayText.includes("**")) { displayText = displayText.replace( /\*\*([^*]+)\*\*/g, "$1" ); } if (displayText.indexOf("\n") >= 0) { displayText = displayText.replace(/\n/g, "
"); } const userBubble = createBubble(displayText, "user"); chatMessagesListContainer.appendChild(userBubble); textInput.value = ""; // clear input textInput.style.height = "auto"; // reset height } // Reset inactivity timer on user message inactivityMessageSent = false; startOrResetInactivityTimer(); const typingBubble = createBubble("...", "bot"); chatMessagesListContainer.appendChild(typingBubble); // Save UUID in session storage let sessionUUID = sessionStorage.getItem("sessionUUID"); if (!sessionUUID) { sessionUUID = generateUUID(); sessionStorage.setItem("sessionUUID", sessionUUID); } const response = await fetch( "https://main.tuluhealth.com/v1/web/chat/?instance=medantabot", { method: "POST", body: JSON.stringify({ query: question, project_id: projectKey, id: chatId || sessionUUID, }), headers: { "Content-Type": "application/json", }, } ); if (!response.ok) { typingBubble.innerHTML = "

An error occured in serving your query. please try again

"; scrollToBottom(); return; } const reader = response.body.getReader(); const decoder = new TextDecoder("utf-8"); let botAnswer = ""; while (true) { const { done, value } = await reader.read(); if (done) break; botAnswer += decoder.decode(value, { stream: true }); typingBubble.innerHTML = marked.parse(botAnswer); scrollToBottom(); } typingBubble.remove(); const botBubble = createBubble(marked.parse(botAnswer), "bot"); chatMessagesListContainer.appendChild(botBubble); scrollToBottom(); // Force social icons after the thank-you line, even if no links/marker are present if ( isMedantaClient && /Thank you for taking this assessment\.?/i.test(botAnswer) ) { renderSocialShareOptions(botBubble); } // Reset inactivity timer after bot reply as well startOrResetInactivityTimer(); // Render choice buttons for Medanta based on the latest bot message, directly under it renderOptionButtonsFrom(botAnswer, botBubble); // Medanta: after assessment result, show only the first FAQ question button if ( isMedantaClient && !medantaFaqShown && /Prostate\s+Cancer\s+Risk\s+Level:/i.test(botAnswer) ) { medantaFaqShown = true; medantaQuestionStep = 0; // Track assessment completion const riskLevelMatch = botAnswer.match( /Prostate\s+Cancer\s+Risk\s+Level:\s*(LOW|HIGH)/i ); logMedantaAnalyticsEvent("assessment_completed", { risk_level: riskLevelMatch ? riskLevelMatch[1].toUpperCase() : null, }); // Render a single button to start FAQs renderFaqFirstButton(botBubble); } // Track assessment start (when Q1 is shown) if ( isMedantaClient && /How old are you/i.test(botAnswer) && /\(a\)|\(b\)|\(c\)|\(d\)|\(e\)/i.test(botAnswer) ) { logMedantaAnalyticsEvent("assessment_started"); medantaQuestionStep = 1; } } // speech to text (voice input) let recognition; let isListening = false; if ("webkitSpeechRecognition" in window) { recognition = new webkitSpeechRecognition(); recognition.continuous = false; recognition.interimResults = false; recognition.lang = "en-US"; recognition.onstart = () => { isListening = true; microphoneButton.classList.add("listening"); }; recognition.onend = () => { isListening = false; microphoneButton.classList.remove("listening"); }; recognition.onresult = (e) => { const transcript = e.results[0][0].transcript; textInput.value = transcript; }; } else { microphoneButton.disabled = true; microphoneButton.title = "Speech recognition not supported in this browser"; } microphoneButton.addEventListener("click", (e) => { e.stopPropagation(); // Prevent container click event if (!recognition) return; if (!isListening) recognition.start(); else recognition.stop(); }); if (!isMedantaClient) { document .querySelectorAll(".suggestionsButton") .forEach((suggestionBtn) => { suggestionBtn.addEventListener("click", (e) => { e.stopPropagation(); // Prevent container click event const question = suggestionBtn.innerHTML.trim(); textInput.value = question; sendMessage(question); startOrResetInactivityTimer(); }); }); } // Enforce buttons-only UI for Medanta: hide text input bar and mic/send if (isMedantaClient && textInputBar) { textInputBar.style.setProperty("display", "none", "important"); } } async function loadLibrary(src) { return new Promise((resolve, reject) => { const script = document.createElement("script"); script.src = src; script.onload = resolve; script.onerror = reject; document.head.appendChild(script); }); } loadLibrary("https://cdn.jsdelivr.net/npm/marked/marked.min.js"); // Remove Tailwind CDN - use our exact equivalent styles instead document.addEventListener("DOMContentLoaded", onDomContentLoaded); })();