document.addEventListener('DOMContentLoaded', function() { console.log('DOM loaded - initializing builder'); // Initialize preview mode state let previewModeActive = false; // Preview Mode Toggle const previewToggle = document.getElementById('preview-toggle'); if (previewToggle) { console.log('Preview toggle found, adding event listener'); document.addEventListener('click', function(e) { // Hacky way: find any click anywhere near the toggle and check status if (e.target.closest('.toggle-switch') || e.target.id === 'preview-toggle') { // Give time for the checkbox to update setTimeout(function() { previewModeActive = previewToggle.checked; console.log('Preview toggle changed to:', previewModeActive); // Add a direct style change to visually confirm toggle works document.querySelector('.toggle-label').style.color = previewModeActive ? 'blue' : ''; togglePreviewMode(previewModeActive); }, 50); } }); } else { console.error('Preview toggle element not found!'); } // Function to toggle preview mode function togglePreviewMode(active) { console.log('Toggle preview mode called with active =', active); // Use inline styles for preview mode // This directly styles elements without relying on classes const styleId = 'preview-mode-style'; let styleElement = document.getElementById(styleId); if (active) { // Create style element if it doesn't exist if (!styleElement) { styleElement = document.createElement('style'); styleElement.id = styleId; document.head.appendChild(styleElement); } // Find all textareas with content to mark for display const contentTextareas = []; document.querySelectorAll('textarea').forEach(textarea => { if (textarea.value && textarea.value.trim()) { // Get the ID for later targeting contentTextareas.push('#' + textarea.id); // Mark parents for visibility const field = textarea.closest('.field'); if (field) field.classList.add('has-content'); const component = textarea.closest('.component-card'); if (component) component.classList.add('has-content'); const stage = textarea.closest('.stage-section'); if (stage) stage.classList.add('has-content'); // Ensure stage is expanded if (stage) { const stageBody = stage.querySelector('.stage-body'); if (stageBody) stageBody.style.display = 'block'; } } }); // Apply direct CSS to create preview mode styleElement.textContent = ` /* Hide module selectors and empty fields */ .module-selector { display: none !important; } .field:not(.has-content) { display: none !important; } /* Hide empty components and sections */ .component-card:not(.has-content) { display: none !important; } .stage-section:not(.has-content) { display: none !important; } /* Hide template selector */ .protocol-template-selector { display: none !important; } /* Expand all sections */ .stage-body { display: block !important; } /* Make textareas read-only appearance */ textarea { border: none !important; background-color: transparent !important; padding: 0 !important; min-height: unset !important; height: auto !important; resize: none !important; pointer-events: none !important; outline: none !important; box-shadow: none !important; } /* Only show filled textareas */ textarea:not(${contentTextareas.join(',')}) { display: none !important; } /* Clean styling for components */ .component-header { background-color: transparent !important; border-bottom: none !important; padding-bottom: 0 !important; } .component-short-label { display: none !important; } /* Improved typography for preview mode */ .component-header h3 { font-size: 1.4rem !important; margin-bottom: 1rem !important; color: #000 !important; border-bottom: 1px solid #eee !important; padding-bottom: 0.5rem !important; } `; // Replace textareas with divs for better display in preview mode updatePreviewContent(); // Make other fields read-only document.querySelectorAll('#community-name, #protocol-summary').forEach(el => { el.setAttribute('readonly', 'readonly'); }); } else { // Remove preview styles if (styleElement) { styleElement.textContent = ''; } // Remove preview content divs and show textareas again document.querySelectorAll('.preview-content').forEach(div => { const textareaId = div.dataset.forTextarea; if (textareaId) { const textarea = document.getElementById(textareaId); if (textarea) { textarea.style.display = ''; } } div.parentNode.removeChild(div); }); // Make fields editable again document.querySelectorAll('textarea, #community-name, #protocol-summary').forEach(el => { el.removeAttribute('readonly'); }); // Remove content markers document.querySelectorAll('.has-content').forEach(el => { el.classList.remove('has-content'); }); // Reset any display properties that were directly set document.querySelectorAll('.stage-body').forEach(el => { el.style.display = ''; }); } } // Function to mark components and stages that have content function markComponentsWithContent() { // First reset all markers document.querySelectorAll('.has-content').forEach(el => { el.classList.remove('has-content'); }); // Mark fields with content document.querySelectorAll('textarea').forEach(textarea => { if (textarea.value && textarea.value.trim()) { const field = textarea.closest('.field'); if (field) field.classList.add('has-content'); const component = textarea.closest('.component-card'); if (component) component.classList.add('has-content'); const stage = textarea.closest('.stage-section'); if (stage) stage.classList.add('has-content'); } }); // Show all expanded sections that have content document.querySelectorAll('.stage-section.has-content').forEach(stage => { const stageBody = stage.querySelector('.stage-body'); if (stageBody) { stageBody.style.display = 'block'; const toggleBtn = stage.querySelector('.toggle-btn'); if (toggleBtn) { toggleBtn.textContent = '-'; } } }); } // Load module data let allModules = {}; try { // Check if moduleData is defined (from modules.js) if (typeof moduleData !== 'undefined') { allModules = moduleData; console.log('Module data loaded from modules.js'); } } catch (e) { console.error('Error loading module data:', e); allModules = {}; } // Load and process template data let templates = []; try { // Check if raw templates are defined (from templates.js) if (typeof rawProtocolTemplates !== 'undefined' && typeof templateMapper !== 'undefined') { console.log('Raw protocol templates loaded from templates.js'); // Process each template to use module references rawProtocolTemplates.forEach(rawTemplate => { const processedTemplate = templateMapper.convertTemplateToModules(rawTemplate, allModules); templates.push(processedTemplate); }); console.log('Processed templates to use module references:', templates); } } catch (e) { console.error('Error processing protocol templates:', e); templates = []; } // Protocol data structure let protocol = { metadata: { communityName: "", summary: "" }, stages: {} }; // DOM elements const stageHeaders = document.querySelectorAll('.stage-header'); console.log('Found stage headers:', stageHeaders.length); const stageContents = document.querySelectorAll('.stage-body'); console.log('Found stage bodies:', stageContents.length); const moduleSelects = document.querySelectorAll('.module-select'); console.log('Found module selects:', moduleSelects.length); const protocolTemplateSelect = document.getElementById('protocol-template'); const communityNameInput = document.getElementById('community-name'); const protocolSummaryTextarea = document.getElementById('protocol-summary'); const exportBtn = document.getElementById('export-btn'); const exportMdBtn = document.getElementById('export-md'); const exportPdfBtn = document.getElementById('export-pdf'); const exportJsonBtn = document.getElementById('export-json'); const importJsonInput = document.getElementById('import-json'); const importBtn = document.getElementById('import-btn'); // Populate protocol template select if (protocolTemplateSelect && templates.length > 0) { templates.forEach(template => { const option = document.createElement('option'); option.value = template.id; option.textContent = template.title; protocolTemplateSelect.appendChild(option); }); // Add template selection event handler protocolTemplateSelect.addEventListener('change', function() { const selectedTemplateId = this.value; if (selectedTemplateId) { // Find the selected template const selectedTemplate = templates.find(t => t.id === selectedTemplateId); if (selectedTemplate) { console.log('Applying template:', selectedTemplate); console.log('Selected template:', selectedTemplate.title); // Reset protocol data while preserving metadata protocol = { metadata: { communityName: communityNameInput.value || "", summary: protocolSummaryTextarea.value || "" }, stages: {}, templateId: selectedTemplate.id, templateTitle: selectedTemplate.title, templateDescription: selectedTemplate.description }; // If summary is empty, use template description if (!protocol.metadata.summary) { protocolSummaryTextarea.value = selectedTemplate.description; protocol.metadata.summary = selectedTemplate.description; } // Apply the template module references to the form for (const stageId in selectedTemplate.moduleRefs) { if (!protocol.stages[stageId]) { protocol.stages[stageId] = {}; } for (const componentId in selectedTemplate.moduleRefs[stageId]) { if (!protocol.stages[stageId][componentId]) { protocol.stages[stageId][componentId] = {}; } for (const fieldId in selectedTemplate.moduleRefs[stageId][componentId]) { const moduleId = selectedTemplate.moduleRefs[stageId][componentId][fieldId]; const textarea = document.getElementById(fieldId); if (textarea) { // Find the module with this ID let moduleContent = ''; for (const category in allModules) { const foundModule = allModules[category].find(m => m.id === moduleId); if (foundModule) { moduleContent = foundModule.content; break; } } // Apply the module content to the textarea textarea.value = moduleContent; // Store in protocol data protocol.stages[stageId][componentId][fieldId] = moduleContent; // If there's a template selector for this field, update it const moduleSelector = document.querySelector(`select.module-select[data-field-id="${fieldId}"][data-component-id="${componentId}"]`); if (moduleSelector) { moduleSelector.value = moduleId; } } } } } // Expand all sections to show the populated content stageContents.forEach(content => { content.style.display = 'block'; const toggleBtn = content.parentElement.querySelector('.toggle-btn'); if (toggleBtn) { toggleBtn.textContent = '-'; } }); // Update preview mode if active if (previewModeActive) { markComponentsWithContent(); } } } else { // Clear all fields if "Create Your Own" is selected document.querySelectorAll('textarea').forEach(textarea => { textarea.value = ''; }); // Reset protocol data protocol = { stages: {} }; // Collapse all sections stageContents.forEach(content => { content.style.display = 'none'; const toggleBtn = content.parentElement.querySelector('.toggle-btn'); if (toggleBtn) { toggleBtn.textContent = '+'; } }); // Update preview mode if active if (previewModeActive) { markComponentsWithContent(); } } }); } // Initialize all module selects populateModuleSelects(); // Module selection handlers moduleSelects.forEach(select => { select.addEventListener('change', function() { const fieldId = this.getAttribute('data-field-id'); const componentId = this.getAttribute('data-component-id'); const targetTextarea = document.getElementById(fieldId); if (targetTextarea && this.value) { // Find the selected module for (const category in allModules) { const selectedModule = allModules[category].find(m => m.id === this.value); if (selectedModule) { targetTextarea.value = selectedModule.content; // Update protocol data updateProtocolData(); // Update preview mode if active if (previewModeActive) { markComponentsWithContent(); } break; } } } }); }); // Function to populate module select dropdowns function populateModuleSelects() { console.log('Populating module selects...'); console.log('Available module categories:', Object.keys(allModules)); // Debugging: Log all modules to check componentId and fieldId console.log('All module mapping:'); for (const category in allModules) { console.log(`Category: ${category}`); allModules[category].forEach(module => { console.log(` Module: ${module.id}, Component: ${module.componentId}, Field: ${module.fieldId}`); }); } moduleSelects.forEach(select => { const fieldId = select.getAttribute('data-field-id'); const componentId = select.getAttribute('data-component-id'); console.log(`Processing module select for fieldId: ${fieldId}, componentId: ${componentId}`); // Clear existing options except the first one while (select.options.length > 1) { select.remove(1); } // Find modules that match this field and component let hasOptions = false; // Always show select first - we'll hide it later if no options found select.closest('.module-selector').style.display = 'flex'; // Check for case matching issues and missing references for (const category in allModules) { let exactMatches = allModules[category].filter(m => m.fieldId === fieldId && m.componentId === componentId ); let caseInsensitiveMatches = allModules[category].filter(m => m.fieldId.toLowerCase() === fieldId.toLowerCase() && m.componentId.toLowerCase() === componentId.toLowerCase() && !exactMatches.includes(m) ); if (caseInsensitiveMatches.length > 0) { console.warn(`Found ${caseInsensitiveMatches.length} case-insensitive matches for ${componentId}/${fieldId}. Consider fixing these module references.`); // Add case-insensitive matches to the collection caseInsensitiveMatches.forEach(module => { // Create a copy with corrected references const correctedModule = { ...module, componentId: componentId, fieldId: fieldId }; // Add to the exact matches exactMatches.push(correctedModule); }); } if (exactMatches.length > 0) { console.log(`Found ${exactMatches.length} modules in category ${category} for ${componentId}/${fieldId}`); hasOptions = true; // Don't use option groups - add options directly to the select // This avoids showing category labels which can be confusing exactMatches.forEach(module => { const option = document.createElement('option'); option.value = module.id; // Use the module title directly from the definition // This relies on proper module titles being defined in modules.js option.textContent = module.title; // Add directly to select instead of to a group select.appendChild(option); }); } } // If no modules found, hide the selector if (!hasOptions) { console.log(`No modules found for ${componentId}/${fieldId}, hiding selector`); select.closest('.module-selector').style.display = 'none'; } }); } // Update protocol data from form inputs function updateProtocolData() { // Update metadata protocol.metadata = { communityName: communityNameInput.value || "", summary: protocolSummaryTextarea.value || "" }; // Reset the stages data protocol.stages = {}; // Get all textareas and their values const textareas = document.querySelectorAll('textarea'); textareas.forEach(textarea => { const fieldId = textarea.id; const fieldValue = textarea.value; // Skip empty fields if (!fieldValue || !fieldValue.trim()) return; // Find the component and stage for this field const componentCard = textarea.closest('.component-card'); if (!componentCard) return; const componentId = componentCard.id.replace('component-', ''); const stageSection = textarea.closest('.stage-section'); if (!stageSection) return; const stageId = stageSection.id.replace('stage-', ''); // Initialize stage and component if they don't exist if (!protocol.stages[stageId]) { protocol.stages[stageId] = {}; } if (!protocol.stages[stageId][componentId]) { protocol.stages[stageId][componentId] = {}; } // Set the field value protocol.stages[stageId][componentId][fieldId] = fieldValue; }); // If a template is selected, preserve the template information const selectedTemplateId = protocolTemplateSelect ? protocolTemplateSelect.value : ''; if (selectedTemplateId) { const selectedTemplate = templates.find(t => t.id === selectedTemplateId); if (selectedTemplate) { protocol.templateId = selectedTemplateId; protocol.templateTitle = selectedTemplate.title; protocol.templateDescription = selectedTemplate.description; } } // Remove empty components and stages for (const stageId in protocol.stages) { // Check if stage has any non-empty components const stageComponents = protocol.stages[stageId]; let stageHasContent = false; for (const componentId in stageComponents) { // Check if component has any fields const component = stageComponents[componentId]; const fieldCount = Object.keys(component).length; if (fieldCount > 0) { stageHasContent = true; } else { // Remove empty component delete stageComponents[componentId]; } } // If stage has no content, remove it if (!stageHasContent) { delete protocol.stages[stageId]; } } } // Export to Markdown exportMdBtn.addEventListener('click', function(e) { e.preventDefault(); updateProtocolData(); try { // Use community name if available, otherwise default const communityName = protocol.metadata.communityName || "Community"; let markdown = `# ${communityName}\n# CommunityDispute\n\n`; // Include protocol summary if available if (protocol.metadata.summary) { markdown += `${protocol.metadata.summary}\n\n`; markdown += '---\n\n'; } // Template information removed as requested // Loop through the stages in order stageHeaders.forEach(header => { const stageId = header.getAttribute('data-stage'); const stageName = header.querySelector('h2').textContent; let stageContent = ''; let hasContent = false; // Get components for this stage const stageComponents = protocol.stages[stageId]; if (stageComponents) { // Loop through components for (const componentId in stageComponents) { const componentCard = document.getElementById(`component-${componentId}`); if (componentCard) { const componentName = componentCard.querySelector('h3').textContent; let componentContent = ''; let componentHasContent = false; // Loop through fields for (const fieldId in stageComponents[componentId]) { const fieldValue = stageComponents[componentId][fieldId]; // Skip empty fields if (fieldValue && fieldValue.trim()) { // Skip the field label, just add the content componentContent += `${fieldValue}\n\n`; componentHasContent = true; hasContent = true; } } // Only add component if it has content if (componentHasContent) { stageContent += `### ${componentName}\n\n${componentContent}`; } } } } // Only add stage if it has content if (hasContent) { // Get the stage section ID (e.g., "Intake") const stageSection = header.closest('.stage-section'); const stageSectionId = stageSection ? stageSection.id.replace('stage-', '') : ''; const stageSectionTitle = stageSectionId ? stageSectionId.charAt(0).toUpperCase() + stageSectionId.slice(1) : ''; // Use just the section title to avoid duplication if (stageSectionTitle) { markdown += `## ${stageSectionTitle}\n\n${stageContent}`; } else { markdown += `## ${stageName}\n\n${stageContent}`; } } }); // Create and download the file downloadFile('communityDispute.md', markdown); } catch (error) { console.error('Error generating Markdown:', error); alert('Failed to generate Markdown. Please try again.'); } }); // Export to PDF exportPdfBtn.addEventListener('click', function(e) { e.preventDefault(); updateProtocolData(); try { // Check if jspdf is properly loaded if (typeof window.jspdf === 'undefined') { console.error('jsPDF library not loaded properly'); alert('PDF export is currently unavailable. The required library failed to load.'); return; } // Create a styled HTML version for PDF export const { jsPDF } = window.jspdf; const doc = new jsPDF(); // Set consistent text width parameters const margins = { left: 14, right: 14, pageWidth: 210, // A4 width in mm (portrait) width: 182 // We'll use this as the consistent text width - should be pageWidth - left - right }; let yPos = 20; // Use community name if available, otherwise default const communityName = protocol.metadata.communityName || "Community"; // Add title with line break doc.setFontSize(18); doc.text(`${communityName}\nCommunityDispute`, 105, yPos, { align: 'center' }); yPos += 25; // Page width indicator removed // Add protocol summary if available if (protocol.metadata.summary) { doc.setFontSize(12); const summaryLines = doc.splitTextToSize(protocol.metadata.summary, margins.width); doc.text(summaryLines, margins.left, yPos); yPos += summaryLines.length * 7 + 8; } // Template information removed as requested // Set up direct rendering approach to avoid duplication const sectionsToProcess = []; // Find all unique stage sections with content document.querySelectorAll('.stage-section').forEach(section => { const sectionId = section.id.replace('stage-', ''); const sectionTitle = sectionId.charAt(0).toUpperCase() + sectionId.slice(1); const componentsWithContent = []; // Look for components with content in this section section.querySelectorAll('.component-card').forEach(componentCard => { const componentId = componentCard.id.replace('component-', ''); // Check if this component has content in the protocol data const stageId = section.id.replace('stage-', ''); if (protocol.stages[stageId] && protocol.stages[stageId][componentId] && Object.keys(protocol.stages[stageId][componentId]).length > 0) { componentsWithContent.push({ id: componentId, element: componentCard, name: componentCard.querySelector('h3').textContent }); } }); // Only include sections that have components with content if (componentsWithContent.length > 0) { sectionsToProcess.push({ id: sectionId, title: sectionTitle, components: componentsWithContent }); } }); // Process each section directly to avoid duplication sectionsToProcess.forEach(section => { // Add page break if needed if (yPos > 250) { doc.addPage(); yPos = 20; } // Add section header only once (e.g., "Intake") doc.setFontSize(18); doc.text(section.title, margins.left, yPos); yPos += 15; // Process components in this section section.components.forEach(component => { // Add page break if needed if (yPos > 250) { doc.addPage(); yPos = 20; } // Add component heading doc.setFontSize(14); doc.text(component.name, margins.left, yPos); yPos += 8; // Get fields for this component const stageId = section.id; const componentId = component.id; if (protocol.stages[stageId] && protocol.stages[stageId][componentId]) { // Loop through fields for (const fieldId in protocol.stages[stageId][componentId]) { const fieldValue = protocol.stages[stageId][componentId][fieldId]; // Skip empty fields if (fieldValue && fieldValue.trim()) { // Add page break if needed if (yPos > 250) { doc.addPage(); yPos = 20; } // Format field content with larger font size (12) doc.setFontSize(12); // Process markdown to plaintext for PDF let processedContent = fieldValue; try { // Strip HTML tags from rendered markdown to get clean text const temp = document.createElement('div'); temp.innerHTML = marked.parse(fieldValue); // Convert links to format: text (url) const links = temp.querySelectorAll('a'); links.forEach(link => { const linkText = link.textContent; const href = link.getAttribute('href'); // Only modify if href is different from text if (href && href !== linkText) { const replacement = `${linkText} (${href})`; link.textContent = replacement; } }); // Bold text handling const boldElements = temp.querySelectorAll('strong, b'); boldElements.forEach(el => { el.textContent = `*${el.textContent}*`; // Surround with asterisks }); // Get plain text content processedContent = temp.textContent; } catch (error) { console.error('Error processing Markdown for PDF:', error); } // Use consistent width for all text const textLines = doc.splitTextToSize(processedContent, margins.width); doc.text(textLines, margins.left, yPos); yPos += textLines.length * 6 + 8; // Slightly increase spacing for larger font } } } }); }); // Save the PDF doc.save('communityDispute.pdf'); } catch (error) { console.error('Error generating PDF:', error); alert('Failed to generate PDF. Please try again or use another export format.'); } }); // Export to JSON exportJsonBtn.addEventListener('click', function(e) { e.preventDefault(); updateProtocolData(); const jsonData = JSON.stringify(protocol, null, 2); downloadFile('communityDispute.json', jsonData); }); // Import from JSON importBtn.addEventListener('click', function() { importJsonInput.click(); }); importJsonInput.addEventListener('change', function(event) { const file = event.target.files[0]; if (file) { const reader = new FileReader(); reader.onload = function(e) { try { const importedProtocol = JSON.parse(e.target.result); protocol = importedProtocol; // Populate metadata fields if present if (protocol.metadata) { if (protocol.metadata.communityName) { communityNameInput.value = protocol.metadata.communityName; } if (protocol.metadata.summary) { protocolSummaryTextarea.value = protocol.metadata.summary; } } // Populate the component fields with the imported data for (const stageId in protocol.stages) { for (const componentId in protocol.stages[stageId]) { for (const fieldId in protocol.stages[stageId][componentId]) { const textarea = document.getElementById(fieldId); if (textarea) { textarea.value = protocol.stages[stageId][componentId][fieldId]; } } } } // If the imported protocol has template information, select that template if (protocol.templateId && protocolTemplateSelect) { protocolTemplateSelect.value = protocol.templateId; // Update template description if (protocol.templateDescription && templateDescription) { templateDescription.textContent = protocol.templateDescription; templateDescription.style.display = 'block'; } // Expand all sections stageContents.forEach(content => { content.style.display = 'block'; const toggleBtn = content.parentElement.querySelector('.toggle-btn'); if (toggleBtn) { toggleBtn.textContent = '-'; } }); } else { // If no template, reset the template selector if (protocolTemplateSelect) { protocolTemplateSelect.value = ''; } if (templateDescription) { templateDescription.textContent = ''; templateDescription.style.display = 'none'; } } // Update preview mode if active if (previewModeActive) { markComponentsWithContent(); } alert('Protocol imported successfully!'); } catch (error) { alert('Failed to import protocol. Invalid JSON format.'); console.error(error); } }; reader.readAsText(file); } }); // Helper function to download a file function downloadFile(filename, content) { const element = document.createElement('a'); element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(content)); element.setAttribute('download', filename); element.style.display = 'none'; document.body.appendChild(element); element.click(); document.body.removeChild(element); } // Save data when inputs change document.querySelectorAll('textarea').forEach(textarea => { textarea.addEventListener('input', function() { updateProtocolData(); // Update preview mode if active if (previewModeActive) { markComponentsWithContent(); } }); }); // Function to update preview content with Markdown support function updatePreviewContent() { // First, clear any existing preview divs document.querySelectorAll('.preview-content').forEach(div => { div.parentNode.removeChild(div); }); // Show all textareas again document.querySelectorAll('textarea').forEach(textarea => { textarea.style.display = ''; }); // Then create new preview divs for all textareas with content document.querySelectorAll('textarea').forEach(textarea => { if (textarea.value && textarea.value.trim()) { // Create a div to replace the textarea for preview const previewDiv = document.createElement('div'); previewDiv.className = 'preview-content'; // Parse Markdown and set as HTML content try { previewDiv.innerHTML = marked.parse(textarea.value); } catch (error) { console.error('Error parsing Markdown:', error); previewDiv.textContent = textarea.value; } previewDiv.dataset.forTextarea = textarea.id; // Hide the textarea and insert the div textarea.style.display = 'none'; textarea.parentNode.insertBefore(previewDiv, textarea.nextSibling); } }); } // Update preview content when preview mode is toggled document.getElementById('preview-toggle').addEventListener('change', function() { if (this.checked) { // Wait for the preview mode styles to apply, then update content setTimeout(updatePreviewContent, 100); } }); // Update preview content when module content changes document.querySelectorAll('.module-select').forEach(select => { select.addEventListener('change', function() { setTimeout(() => { if (document.getElementById('preview-toggle').checked) { updatePreviewContent(); } }, 100); }); }); // Update preview content on window resize window.addEventListener('resize', function() { if (document.getElementById('preview-toggle').checked) { updatePreviewContent(); } }); console.log('Builder initialization complete'); });