PageSourceSearch

https://www.trybased.com/faq-widget.js

js trybased.com collected 2026-10-02 01:50:14 UTC 32,231 bytes, 616 lines download raw bytes

1/**
2 * FAQ Widget Loader Script
3 * This script can be embedded on any website to display FAQ content
4 * Usage:
5 * <div id="faq-widget-container"></div>
6 * <script src="https://www.trybased.com/faq-widget.js" data-workspace-id="WORKSPACE_ID" data-container="#faq-widget-container"></script>
7 */
8
9(function () {
10    'use strict';
11
12    // Get configuration from script tag
13    // Try multiple methods to find the script tag (for compatibility with different embed environments)
14    let scriptTag = document.currentScript;
15
16    // Fallback 1: Find by data attributes
17    if (!scriptTag) {
18        scriptTag = document.querySelector('script[data-workspace-id], script[data-link-id]');
19    }
20
21    // Fallback 2: Find by src containing faq-widget.js
22    if (!scriptTag) {
23        const scripts = document.querySelectorAll('script[src*="faq-widget.js"]');
24        if (scripts.length > 0) {
25            // Find the one with data attributes, or use the last one (most likely the one that just loaded)
26            scriptTag = Array.from(scripts).find(s =>
27                s.hasAttribute('data-workspace-id') || s.hasAttribute('data-link-id')
28            ) || scripts[scripts.length - 1];
29        }
30    }
31
32    // Fallback 3: Find any script with faq-widget in src and check all scripts
33    if (!scriptTag) {
34        const allScripts = document.querySelectorAll('script[src]');
35        for (let i = allScripts.length - 1; i >= 0; i--) {
36            const script = allScripts[i];
37            if (script.src && script.src.includes('faq-widget.js')) {
38                if (script.hasAttribute('data-workspace-id') || script.hasAttribute('data-link-id')) {
39                    scriptTag = script;
40                    break;
41                }
42            }
43        }
44    }
45
46    if (!scriptTag) {
47        console.error('FAQ Widget: Script tag not found. Make sure the script tag includes data-link-id or data-workspace-id attributes.');
48        return;
49    }
50
51    // Get container selector first (needed for fallback)
52    const containerSelector = (scriptTag.getAttribute('data-container') || '#faq-widget-container').startsWith('#') || (scriptTag.getAttribute('data-container') || '#faq-widget-container').startsWith('.')
53        ? (scriptTag.getAttribute('data-container') || '#faq-widget-container')
54        : '#' + (scriptTag.getAttribute('data-container') || 'faq-widget-container');
55
56    // Find container element early (needed for fallback config reading)
57    let container = document.querySelector(containerSelector);
58
59    // If container not found, try default
60    if (!container) {
61        container = document.querySelector('#faq-widget-container');
62    }
63
64    // Build config from script tag
65    let config = {
66        workspaceId: scriptTag.getAttribute('data-workspace-id'),
67        linkId: scriptTag.getAttribute('data-link-id'),
68        container: scriptTag.getAttribute('data-container') || '#faq-widget-container',
69        theme: scriptTag.getAttribute('data-theme') || 'auto',
70        displayMode: (scriptTag.getAttribute('data-display-mode') || 'full').toLowerCase(),
71        baseUrl: scriptTag.getAttribute('data-base-url') || 'https://aiknowledgebase-7427e.web.app'
72    };
73
74    // Debug logging for display mode
75    console.log('FAQ Widget: Display mode:', config.displayMode);
76
77    // Fallback: If script tag doesn't have attributes, try reading from container element's data attributes
78    // This is especially important for Bubble.io which may strip script tag attributes
79    if (container) {
80        if (!config.workspaceId) {
81            config.workspaceId = container.getAttribute('data-workspace-id');
82        }
83        if (!config.linkId) {
84            config.linkId = container.getAttribute('data-link-id');
85        }
86        if (container.getAttribute('data-container')) {
87            config.container = container.getAttribute('data-container');
88        }
89        if (container.getAttribute('data-theme')) {
90            config.theme = container.getAttribute('data-theme');
91        }
92        // Always check container for display-mode (Bubble.io may strip script attributes)
93        if (container.getAttribute('data-display-mode')) {
94            config.displayMode = container.getAttribute('data-display-mode').toLowerCase();
95        }
96    }
97
98    // Fallback: Try reading from a separate config script tag
99    if (!config.workspaceId && !config.linkId) {
100        const configScript = document.querySelector('script[type="application/json"][data-faq-widget-config]');
101        if (configScript) {
102            try {
103                const configData = JSON.parse(configScript.textContent);
104                config.workspaceId = configData.workspaceId || config.workspaceId;
105                config.linkId = configData.linkId || config.linkId;
106                config.container = configData.container || config.container;
107                config.theme = configData.theme || config.theme;
108                config.displayMode = config.displayMode || config.displayMode;
109                config.baseUrl = configData.baseUrl || config.baseUrl;
110            } catch (e) {
111                console.warn('FAQ Widget: Failed to parse config script', e);
112            }
113        }
114    }
115
116    // Fallback: Try reading from global variable
117    if (!config.workspaceId && !config.linkId && window.FAQWidgetConfig) {
118        config.workspaceId = window.FAQWidgetConfig.workspaceId || config.workspaceId;
119        config.linkId = window.FAQWidgetConfig.linkId || config.linkId;
120        config.container = window.FAQWidgetConfig.container || config.container;
121        config.theme = window.FAQWidgetConfig.theme || config.theme;
122        config.displayMode = (window.FAQWidgetConfig?.displayMode || config.displayMode).toLowerCase();
123        config.baseUrl = window.FAQWidgetConfig.baseUrl || config.baseUrl;
124    }
125
126    // Debug logging
127    if (!config.workspaceId && !config.linkId) {
128        console.error('FAQ Widget: workspaceId or linkId is required');
129        console.error('FAQ Widget: Found script tag:', scriptTag);
130        console.error('FAQ Widget: Script tag attributes:', {
131            'data-workspace-id': scriptTag.getAttribute('data-workspace-id'),
132            'data-link-id': scriptTag.getAttribute('data-link-id'),
133            'data-container': scriptTag.getAttribute('data-container'),
134            src: scriptTag.src
135        });
136        if (container) {
137            console.error('FAQ Widget: Container element:', container);
138            console.error('FAQ Widget: Container attributes:', {
139                'data-workspace-id': container.getAttribute('data-workspace-id'),
140                'data-link-id': container.getAttribute('data-link-id'),
141                'data-container': container.getAttribute('data-container')
142            });
143        }
144        console.error('FAQ Widget: All script tags with faq-widget:',
145            Array.from(document.querySelectorAll('script[src*="faq-widget"]')).map(s => ({
146                src: s.src,
147                attributes: Array.from(s.attributes).map(a => `${a.name}="${a.value}"`).join(' ')
148            }))
149        );
150        console.error('FAQ Widget: Please add data-link-id or data-workspace-id to either:');
151        console.error('  1. The script tag: <script ... data-link-id="YOUR_ID"></script>');
152        console.error('  2. The container div: <div id="faq-widget-container" data-link-id="YOUR_ID"></div>');
153        console.error('  3. A config script: <script type="application/json" data-faq-widget-config>{"linkId":"YOUR_ID"}</script>');
154        return;
155    }
156
157    // Ensure container exists
158    if (!container) {
159        console.error(`FAQ Widget: Container not found: ${containerSelector}`);
160        return;
161    }
162
163    // Inject CSS if not already present
164    if (!document.getElementById('faq-widget-styles')) {
165        const style = document.createElement('style');
166        style.id = 'faq-widget-styles';
167        style.textContent = `
168            .faq-widget-loading, .faq-widget-error {
169                font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
170            }
171            @keyframes spin {
172                from { transform: rotate(0deg); }
173                to { transform: rotate(360deg); }
174            }
175            .faq-widget-full, .faq-widget-compact {
176                font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
177            }
178            @keyframes slideDown {
179                from {
180                    opacity: 0;
181                    max-height: 0;
182                }
183                to {
184                    opacity: 1;
185                    max-height: 500px;
186                }
187            }
188        `;
189        document.head.appendChild(style);
190    }
191
192    // Show loading state
193    container.innerHTML = `
194        <div class="faq-widget-loading" style="padding: 2rem; text-align: center;">
195            <div style="width: 2rem; height: 2rem; border: 3px solid #e5e7eb; border-top-color: #3b82f6; border-radius: 50%; animation: spin 1s linear infinite; margin: 0 auto;"></div>
196            <p style="margin-top: 1rem; color: #6b7280;">Loading FAQs...</p>
197        </div>
198    `;
199
200    // Load React and ReactDOM from CDN
201    const loadReact = () => {
202        return new Promise((resolve, reject) => {
203            if (window.React && window.ReactDOM) {
204                resolve();
205                return;
206            }
207
208            const reactScript = document.createElement('script');
209            reactScript.src = 'https://unpkg.com/react@18/umd/react.production.min.js';
210            reactScript.crossOrigin = 'anonymous';
211            reactScript.onload = () => {
212                const reactDOMScript = document.createElement('script');
213                reactDOMScript.src = 'https://unpkg.com/react-dom@18/umd/react-dom.production.min.js';
214                reactDOMScript.crossOrigin = 'anonymous';
215                reactDOMScript.onload = resolve;
216                reactDOMScript.onerror = reject;
217                document.head.appendChild(reactDOMScript);
218            };
219            reactScript.onerror = reject;
220            document.head.appendChild(reactScript);
221        });
222    };
223
224    // Load the widget component
225    const loadWidget = async () => {
226        try {
227            // Load React
228            await loadReact();
229
230            // Load the widget bundle (we'll need to build this separately)
231            // For now, we'll use a simple fetch-based approach
232            const widgetModule = await import(`${config.baseUrl}/faq-widget-bundle.js`);
233
234            // Render widget
235            const { render } = window.ReactDOM;
236            const { default: FAQWidgetEmbed } = widgetModule;
237
238            render(
239                window.React.createElement(FAQWidgetEmbed, {
240                    workspaceId: config.workspaceId,
241                    linkId: config.linkId,
242                    containerId: config.container.replace('#', ''),
243                    theme: config.theme,
244                    displayMode: config.displayMode
245                }),
246                container
247            );
248
249        } catch (error) {
250            console.error('FAQ Widget: Error loading widget', error);
251            container.innerHTML = `
252                <div class="faq-widget-error" style="padding: 2rem; text-align: center; color: #dc2626;">
253                    <p>Error loading FAQ widget. Please check your configuration.</p>
254                </div>
255            `;
256        }
257    };
258
259    // Alternative: Simple fetch-based widget (no React dependency)
260    const loadSimpleWidget = async () => {
261        try {
262            const params = new URLSearchParams();
263            if (config.workspaceId) params.append('workspaceId', config.workspaceId);
264            if (config.linkId) params.append('linkId', config.linkId);
265
266            const apiUrl = `https://us-central1-aiknowledgebase-7427e.cloudfunctions.net/getFAQData?${params.toString()}`;
267            console.log('FAQ Widget: Fetching FAQ data from:', apiUrl);
268
269            const response = await fetch(apiUrl, {
270                method: 'GET',
271                headers: {
272                    'Accept': 'application/json'
273                }
274            });
275
276            if (!response.ok) {
277                const errorText = await response.text();
278                console.error('FAQ Widget: API error:', response.status, errorText);
279                throw new Error(`HTTP ${response.status}: ${errorText}`);
280            }
281
282            const data = await response.json();
283            console.log('FAQ Widget: Received data:', {
284                faqsCount: data.faqs?.length || 0,
285                hasSchema: !!data.schema,
286                organization: data.organization?.name
287            });
288
289            let faqs = data.faqs || [];
290            const organization = data.organization || { name: 'Knowledge Base', logoUrl: null };
291
292            // Filter out FAQs with unhelpful answers or no answers
293            const unhelpfulAnswerPatterns = [
294                /does not provide information/i,
295                /not available/i,
296                /couldn't find/i,
297                /no information/i,
298                /unable to/i,
299                /cannot find/i,
300                /no relevant/i,
301                /context does not/i
302            ];
303
304            faqs = faqs.filter(faq => {
305                // Must have an answer
306                if (!faq.answer || faq.answer.trim() === '') {
307                    return false;
308                }
309
310                // Check if answer is unhelpful
311                const answerLower = faq.answer.toLowerCase();
312                const isUnhelpful = unhelpfulAnswerPatterns.some(pattern =>
313                    pattern.test(faq.answer)
314                );
315
316                // Also check for generic "not available" messages
317                const isGenericUnavailable = answerLower.includes('answer not available') ||
318                    answerLower.includes('not available in the knowledge base');
319
320                return !isUnhelpful && !isGenericUnavailable;
321            });
322
323            console.log(`FAQ Widget: Filtered ${data.faqs?.length || 0} FAQs down to ${faqs.length} with helpful answers`);
324
325            if (faqs.length === 0) {
326                console.warn('FAQ Widget: No FAQs with helpful answers found.');
327            }
328
329            // Store FAQs with answers for accordion functionality
330            window.faqWidgetData = {
331                faqs: faqs,
332                organization: organization,
333                config: config
334            };
335
336            // Inject schema
337            if (data.schema) {
338                const schemaScript = document.createElement('script');
339                schemaScript.type = 'application/ld+json';
340                schemaScript.id = 'faq-widget-schema';
341                schemaScript.textContent = JSON.stringify(data.schema);
342                document.head.appendChild(schemaScript);
343            }
344
345            // Render FAQs
346            if (config.displayMode === 'compact') {
347                if (faqs.length === 0) {
348                    container.innerHTML = `
349                        <div class="faq-widget-compact">
350                            <p style="color: #6b7280; text-align: center; padding: 2rem;">No FAQs available yet. Please add documents with question headings to your knowledge base.</p>
351                        </div>
352                    `;
353                } else {
354                    container.innerHTML = `
355                        <div class="faq-widget-compact">
356                            <div style="display: grid; gap: 0.75rem;">
357                                ${faqs.map((faq, index) => {
358                        const faqId = `faq-compact-${index}`;
359                        // All FAQs at this point have valid answers (already filtered)
360                        const hasAnswer = true;
361                        return `
362                                        <div 
363                                            id="${faqId}-card"
364                                            data-default-border="#e5e7eb"
365                                            data-expanded-border="#bfdbfe"
366                                            data-is-expanded="false"
367                                            style="background: white; border: 1px solid #e5e7eb; border-radius: 0.5rem; overflow: hidden; transition: all 0.2s;"
368                                            onmouseenter="(function(card){ card.setAttribute('data-is-hovered', 'true'); card.style.borderColor = '#3b82f6'; card.style.boxShadow='0 1px 3px rgba(0,0,0,0.1)'; })(this)"
369                                            onmouseleave="(function(card){ card.setAttribute('data-is-hovered', 'false'); card.style.borderColor = '#e5e7eb'; card.style.boxShadow='none'; })(this)"
370                                        >
371                                            <button 
372                                                onclick="event.stopPropagation(); window.faqWidgetToggle && window.faqWidgetToggle('${faqId}', '${faq.question.replace(/'/g, "\\'")}', event)"
373                                                style="width: 100%; display: flex; align-items: start; gap: 0.75rem; padding: 0.75rem; background: none; border: none; text-align: left; cursor: pointer;"
374                                            >
375                                                <span style="font-size: 0.875rem; font-weight: 500; color: #111827; flex: 1;">${faq.question}</span>
376                                                ${hasAnswer ? `
377                                                    <svg id="${faqId}-icon" style="width: 1rem; height: 1rem; color: #6b7280; flex-shrink: 0; transition: transform 0.2s;" fill="none" stroke="currentColor" viewBox="0 0 24 24">
378                                                        <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path>
379                                                    </svg>
380                                                ` : ''}
381                                            </button>
382                                            ${hasAnswer ? `
383                                                <div id="${faqId}-answer" style="display: none; padding: 0 0.75rem 0.75rem; color: #374151; line-height: 1.6; font-size: 0.8125rem; border-top: 1px solid #e5e7eb; margin-top: 0;">
384                                                    <p style="margin: 0.75rem 0 0 0;">${faq.answer}</p>
385                                                </div>
386                                            ` : ''}
387                                        </div>
388                                    `;
389                    }).join('')}
390                            </div>
391                        </div>
392                    `;
393                }
394            } else {
395                container.innerHTML = `
396                    <div class="faq-widget-full" style="min-height: 100vh; background: linear-gradient(to bottom right, #eff6ff, #ffffff, #faf5ff);">
397                        <header style="background: rgba(255, 255, 255, 0.8); backdrop-filter: blur(4px); border-bottom: 1px solid #e5e7eb; position: sticky; top: 0; z-index: 40; padding: 1rem 1.5rem;">
398                            <div style="display: flex; align-items: center; gap: 0.75rem;">
399                                ${organization.logoUrl ? `
400                                    <img src="${organization.logoUrl}" alt="${organization.name}" style="height: 2.5rem; max-width: 200px; object-fit: contain;" />
401                                ` : `
402                                    <div style="height: 2.5rem; width: 2.5rem; background: #3b82f6; color: white; border-radius: 0.5rem; display: flex; align-items: center; justify-content: center; font-size: 1.25rem; font-weight: bold;">
403                                        ${organization.name.charAt(0).toUpperCase()}
404                                    </div>
405                                `}
406                                <span style="font-size: 1.125rem; font-weight: 600; color: #111827;">${organization.name}</span>
407                            </div>
408                        </header>
409                        <div style="max-width: 64rem; margin: 0 auto; padding: 4rem 1.5rem 3rem;">
410                            <div style="text-align: center; margin-bottom: 3rem;">
411                                <h1 style="font-size: 2.25rem; font-weight: bold; color: #111827; margin-bottom: 1rem;">How can we help you today?</h1>
412                                <p style="font-size: 1.25rem; color: #4b5563; max-width: 32rem; margin: 0 auto;">Search our knowledge base or browse frequently asked questions</p>
413                            </div>
414                            <div>
415                                <h2 style="font-size: 1.5rem; font-weight: bold; color: #111827; margin-bottom: 1.5rem; text-align: center;">Frequently Asked Questions</h2>
416                                ${faqs.length === 0 ? `
417                                    <div style="text-align: center; padding: 3rem; background: white; border-radius: 0.75rem; box-shadow: 0 4px 6px -1px rgba(0,0,0,0.1);">
418                                        <p style="font-size: 1.125rem; color: #6b7280; margin-bottom: 0.5rem;">No FAQs available yet</p>
419                                        <p style="font-size: 0.875rem; color: #9ca3af;">Add documents with question headings (containing "?" or starting with question words) to your knowledge base to generate FAQs.<
419/p>
420                                    </div>
421                                ` : `
422                                    <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 1rem;">
423                                        ${faqs.map((faq, index) => {
424                    const faqId = `faq-${index}`;
425                    // All FAQs at this point have valid answers (already filtered)
426                    const hasAnswer = true;
427                    return `
428                                                <div 
429                                                    id="${faqId}-card"
430                                                    style="background: white; border-radius: 0.75rem; box-shadow: 0 4px 6px -1px rgba(0,0,0,0.1); border: 2px solid transparent; transition: all 0.2s; overflow: hidden;"
431                                                    onmouseenter="this.style.borderColor='#bfdbfe'; this.style.boxShadow='0 20px 25px -5px rgba(0,0,0,0.1)'; this.style.transform='translateY(-4px)'"
432                                                    onmouseleave="this.style.borderColor='transparent'; this.style.boxShadow='0 4px 6px -1px rgba(0,0,0,0.1)'; this.style.transform='translateY(0)'"
433                                                >
434                                                    <button 
435                                                        onclick="event.stopPropagation(); window.faqWidgetToggle && window.faqWidgetToggle('${faqId}', '${faq.question.replace(/'/g, "\\'")}', event)"
436                                                        style="width: 100%; padding: 1.5rem; text-align: left; cursor: pointer; background: none; border: none; display: flex; justify-content: space-between; align-items: center;"
437                                                    >
438                                                        <h3 style="font-size: 0.875rem; font-weight: 600; color: #111827; margin: 0; flex: 1;">${faq.question}</h3>
439                                                        ${hasAnswer ? `
440                                                            <svg id="${faqId}-icon" style="width: 1.25rem; height: 1.25rem; color: #6b7280; flex-shrink: 0; margin-left: 0.75rem; transition: transform 0.2s;" fill="none" stroke="currentColor" viewBox="0 0 24 24">
441                                                                <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path>
442                                                            </svg>
443                                                        ` : ''}
444                                                    </button>
445                                                    ${hasAnswer ? `
446                                                        <div id="${faqId}-answer" style="display: none; padding: 0 1.5rem 1.5rem; color: #374151; line-height: 1.75; font-size: 0.875rem; border-top: 1px solid #e5e7eb; margin-top: 0; animation: slideDown 0.3s ease-out;">
447                                                            <p style="margin: 1rem 0 0 0;">${faq.answer}</p>
448                                                        </div>
449                                                    ` : ''}
450                                                </div>
451                                            `;
452                }).join('')}
453                                    </div>
454                                `}
455                            </div>
456                        </div>
457                    </div>
458                `;
459            }
460
461            // Toggle FAQ answer display (accordion functionality)
462            window.faqWidgetToggle = (faqId, question, event) => {
463                // Prevent event bubbling
464                if (event) {
465                    event.stopPropagation();
466                    event.preventDefault();
467                }
468
469                const answerDiv = document.getElementById(`${faqId}-answer`);
470                const icon = document.getElementById(`${faqId}-icon`);
471                const card = document.getElementById(`${faqId}-card`);
472
473                if (!answerDiv) {
474                    console.warn(`FAQ Widget: Could not find answer div for ${faqId}`);
475                    return;
476                }
477
478                // Find the parent container (either faq-widget-compact or faq-widget-full)
479                const widgetContainer = answerDiv.closest('.faq-widget-compact') || answerDiv.closest('.faq-widget-full') || container;
480
481                const isExpanded = answerDiv.style.display !== 'none' && answerDiv.style.display !== '';
482
483                // Close all other FAQs first (accordion behavior - only one open at a time)
484                // Only close FAQs within the same widget container
485                if (!isExpanded) {
486                    // Find all FAQ answer divs within the same container and close them
487                    const allAnswerDivs = widgetContainer.querySelectorAll('[id$="-answer"]');
488                    const allIcons = widgetContainer.querySelectorAll('[id$="-icon"]');
489                    const allCards = widgetContainer.querySelectorAll('[id$="-card"]');
490
491                    allAnswerDivs.forEach(div => {
492                        if (div.id !== `${faqId}-answer` && div.id) {
493                            div.style.display = 'none';
494                        }
495                    });
496
497                    allIcons.forEach(iconEl => {
498                        if (iconEl.id !== `${faqId}-icon` && iconEl.id) {
499                            iconEl.style.transform = 'rotate(0deg)';
500                        }
501                    });
502
503                    allCards.forEach(cardEl => {
504                        if (cardEl.id !== `${faqId}-card` && cardEl.id) {
505                            cardEl.setAttribute('data-is-expanded', 'false');
506                            // Only reset border if not currently being hovered
507                            const isHovered = cardEl.getAttribute('data-is-hovered') === 'true';
508                            if (!isHovered) {
509                                cardEl.style.borderColor = '#e5e7eb';
510                                cardEl.style.boxShadow = 'none';
511                            }
512                        }
513                    });
514                }
515
516                if (isExpanded) {
517                    // Collapse
518                    answerDiv.style.display = 'none';
519                    if (icon) icon.style.transform = 'rotate(0deg)';
520                    if (card) {
521                        card.setAttribute('data-is-expanded', 'false');
522                        // Only reset border if not currently being hovered
523                        const isHovered = card.getAttribute('data-is-hovered') === 'true';
524                        if (!isHovered) {
525                            card.style.borderColor = '#e5e7eb';
526                            card.style.boxShadow = 'none';
527                        }
528                    }
529                } else {
530                    // Expand
531                    answerDiv.style.display = 'block';
532                    if (icon) icon.style.transform = 'rotate(180deg)';
533                    if (card) {
534                        card.setAttribute('data-is-expanded', 'true');
535                        // Only reset border if not currently being hovered
536                        const isHovered = card.getAttribute('data-is-hovered') === 'true';
537                        if (!isHovered) {
538                            card.style.borderColor = '#e5e7eb';
539                            card.style.boxShadow = 'none';
540                        }
541                    }
542
543                    // Update URL for SEO
544                    const newUrl = new URL(window.location);
545                    newUrl.searchParams.set('faq_q', encodeURIComponent(question));
546                    window.history.pushState({ question, type: 'faq', faqId }, '', newUrl);
547
548                    // Update page title for SEO
549                    const originalTitle = document.title;
550                    if (!originalTitle.includes(question)) {
551                        document.title = `${question} - ${originalTitle}`;
552                    }
553
554                    // Scroll the expanded FAQ into view
555                    setTimeout(() => {
556                        if (card) {
557                            card.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
558                        }
559                    }, 100);
560                }
561            };
562
563            // Set up search function (for URL-based navigation)
564            window.faqWidgetSearch = (question) => {
565                // Find the FAQ by question and toggle it
566                if (window.faqWidgetData && window.faqWidgetData.faqs) {
567                    const faqIndex = window.faqWidgetData.faqs.findIndex(f => f.question === question);
568                    if (faqIndex >= 0) {
569                        const faqId = `faq-${faqIndex}`;
570                        window.faqWidgetToggle(faqId, question);
571                    }
572                }
573            };
574
575            // Handle initial URL parameter
576            const urlParams = new URLSearchParams(window.location.search);
577            const initialQuestion = urlParams.get('faq_q');
578            if (initialQuestion) {
579                setTimeout(() => {
580                    window.faqWidgetSearch(decodeURIComponent(initialQuestion));
581                }, 500);
582            }
583
584        } catch (error) {
585            console.error('FAQ Widget: Error loading widget', error);
586            console.error('FAQ Widget: Error stack:', error.stack);
587            container.innerHTML = `
588                <div class="faq-widget-error" style="padding: 2rem; text-align: center; color: #dc2626;">
589                    <p style="font-weight: 600; margin-bottom: 0.5rem;">Error loading FAQ widget</p>
590                    <p style="font-size: 0.875rem; color: #9ca3af;">${error.message}</p>
591                    <p style="font-size: 0.75rem; color: #9ca3af; margin-top: 1rem;">Check the browser console for more details.</p>
592                </div>
593            `;
594        }
595    };
596
597    // Try to load React-based widget first, fallback to simple widget
598    if (config.displayMode === 'full' && window.React) {
599        loadWidget().catch(() => loadSimpleWidget());
600    } else {
601        loadSimpleWidget();
602    }
603
604    // Handle URL updates for SEO
605    if (config.displayMode === 'full') {
606        window.addEventListener('popstate', (event) => {
607            if (event.state && event.state.question) {
608                // Handle back/forward navigation
609                const question = event.state.question;
610                if (window.faqWidgetSearch) {
611                    window.faqWidgetSearch(question);
612                }
613            }
614        });
615    }
616})();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.