robcazin

CFM.js

Jul 19th, 2025
373
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
JavaScript 278.42 KB | Source Code | 0 0
  1.  
  2. // Create a dimensions display element
  3. // const dimensionsDisplay = document.createElement('div');
  4. // dimensionsDisplay.id = 'dimensions-display';
  5. // dimensionsDisplay.style.cssText = `
  6. //     position: fixed;
  7. //     top: 10px;
  8. //     right: 10px;
  9. //     background: rgba(0, 0, 0, 0.7);
  10. //     color: white;
  11. //     padding: 5px 10px;
  12. //     font-size: 14px;
  13. //     font-family: monospace;
  14. //     z-index: 1000;
  15. //     border-radius: 3px;
  16. // `;
  17. // document.body.appendChild(dimensionsDisplay);
  18.  
  19. // // Function to update dimensions in the UI
  20. // const updateDimensionsDisplay = () => {
  21. //     const width = window.innerWidth;
  22. //     const height = window.innerHeight;
  23. //     dimensionsDisplay.textContent = `${width} x ${height}px`;
  24. // };
  25.  
  26. // updateDimensionsDisplay();
  27.  
  28. // window.addEventListener('resize', updateDimensionsDisplay);
  29.  
  30. // Wrap your key functions:
  31. function traceWrapper(fn, name) {
  32.   return function(...args) {
  33.     console.group(`๐Ÿง  ${name}`);
  34.     console.log('Arguments:', args);
  35.     const result = fn.apply(this, args);
  36.     console.log('Result:', result);
  37.     console.groupEnd();
  38.     return result;
  39.   };
  40. }
  41.  
  42. // Then guard against premature loading:
  43. function guard(fn) {
  44.   return function (...args) {
  45.     if (!isAppReady) {
  46.       console.warn(`โณ Skipping ${fn.name} โ€” app not ready`);
  47.       return;
  48.     }
  49.     return fn.apply(this, args);
  50.   };
  51. }
  52.  
  53.  
  54. // ---- Debug Logging Setup ----
  55. const DEBUG_TRACE = false; // set to false to disable tracing
  56. const USE_GUARD = false;
  57.  
  58. // Optional: Remove later by commenting out or deleting these lines// Toggle flag for normalization (set to false for binary threshold, true for normalization)
  59. const USE_NORMALIZATION = true; // Change to true to enable normalization
  60.  
  61. // Fabric composite tuning parameters
  62. const fabricTuning = {
  63.     alphaStrength: 1.0,     // Controls pattern opacity (0.0 - 2.0)
  64.     baseTintStrength: 1.0,  // Controls how much background color affects fabric base (0.0 - 2.0)
  65.     patternContrast: 1.0,   // Controls pattern contrast (0.0 - 3.0)
  66.     shadowMultiplier: 1.0,  // Controls shadow interaction strength (0.0 - 2.0)
  67.     colorVibrance: 1.2,     // Controls color saturation (0.0 - 2.0)
  68.     blendMode: 'auto',      // Blend mode: 'multiply', 'overlay', 'soft-light', 'auto'
  69.     glossyStrength: 1.0     // Controls glossy layer opacity (0.0 - 2.0)
  70. };
  71.  
  72. // Control visibility of fabric tuning controls
  73. const SHOW_FABRIC_CONTROLS = false; // Set to true to show controls, false to hide
  74.  
  75.  
  76. // Debounce function for tuning controls
  77. let fabricRenderTimeout;
  78. function debouncedFabricRender() {
  79.     clearTimeout(fabricRenderTimeout);
  80.     fabricRenderTimeout = setTimeout(() => {
  81.         if (appState.isInFabricMode) {
  82.             renderFabricMockup();
  83.         }
  84.     }, 100); // 100ms debounce
  85. }
  86.  
  87. // App state - Made global for save functionality
  88. window.appState = {
  89.     collections: [],
  90.     colorsData: [],
  91.     currentPattern: null,
  92.     currentLayers: [],
  93.     curatedColors: [],
  94.     layerInputs: [],
  95.     selectedCollection: null,
  96.     cachedLayerPaths: [],
  97.     lastSelectedLayer: null,
  98.     currentScale: 10,
  99.     designer_colors: [],
  100.     originalPattern: null,
  101.     originalCoordinates: null,
  102.     originalLayerInputs: null,
  103.     originalCurrentLayers: null,
  104.     lastSelectedColor: null,
  105.     selectedFurniture: null,
  106.     isInFabricMode: false
  107. };
  108.  
  109. const BACKGROUND_INDEX = 0;
  110. const FURNITURE_BASE_INDEX = 1;
  111. const PATTERN_BASE_INDEX = 2;
  112. let isAppReady = false; // Flag to track if the app is fully initialized
  113.  
  114. // Save to list functionality - Updated: 2025-01-19 v3 - Fixed syntax
  115. window.saveToMyList = function() {
  116.     try {
  117.         // Use global appState reference
  118.         const state = window.appState;
  119.        
  120.         // Validate that we have the required data
  121.         if (!state.currentPattern || !state.currentPattern.name) {
  122.             showSaveNotification('โŒ No pattern selected to save');
  123.             return;
  124.         }
  125.        
  126.         if (!state.selectedCollection || !state.selectedCollection.name) {
  127.             showSaveNotification('โŒ No collection selected');
  128.             return;
  129.         }
  130.        
  131.         if (!state.currentLayers || state.currentLayers.length === 0) {
  132.             showSaveNotification('โŒ No layers to save');
  133.             return;
  134.         }
  135.        
  136.         // Capture current pattern state
  137.         const currentState = {
  138.             pattern: {
  139.                 name: state.currentPattern.name,
  140.                 collection: state.selectedCollection.name,
  141.                 layers: state.currentLayers.map(layer => ({
  142.                     label: layer.label,
  143.                     color: layer.color,
  144.                     isShadow: layer.isShadow || false
  145.                 }))
  146.             },
  147.             timestamp: new Date().toISOString(),
  148.             id: Date.now() // Simple ID generation
  149.         };
  150.        
  151.         console.log('๐Ÿ’พ Saving pattern to list:', currentState);
  152.        
  153.         // Try to save to Shopify customer metafields (if available)
  154.         const customerId = getCustomerId();
  155.         const customerAccessToken = getCustomerAccessToken();
  156.        
  157.         if (customerId && customerAccessToken) {
  158.             saveToShopifyMetafields(currentState).then(function() {
  159.                 console.log('โœ… Saved to Shopify customer metafields');
  160.             }).catch(function(error) {
  161.                 console.log('๐Ÿ”„ Shopify save failed, using localStorage fallback');
  162.                 saveToLocalStorage(currentState);
  163.             });
  164.         } else {
  165.             // Fall back to localStorage for development/testing
  166.             console.log('๐Ÿ“ฑ Customer not authenticated, saving to localStorage');
  167.             saveToLocalStorage(currentState);
  168.         }
  169.        
  170.         // Show success message
  171.         showSaveNotification('โœ… Pattern saved to your list!');
  172.        
  173.     } catch (error) {
  174.         console.error('โŒ Failed to save pattern:', error);
  175.         showSaveNotification('โŒ Failed to save pattern');
  176.     }
  177. };
  178.  
  179. // Save to Shopify customer metafields
  180. function saveToShopifyMetafields(patternData) {
  181.     return new Promise(function(resolve, reject) {
  182.         try {
  183.             var customerId = getCustomerId();
  184.             var customerAccessToken = getCustomerAccessToken();
  185.            
  186.             if (!customerId || !customerAccessToken) {
  187.                 reject(new Error('Customer not authenticated'));
  188.                 return;
  189.             }
  190.  
  191.             console.log('๐Ÿ”„ Saving to Shopify customer metafields...');
  192.            
  193.             fetch('/api/colorFlex/save-pattern', {
  194.                 method: 'POST',
  195.                 headers: {
  196.                     'Content-Type': 'application/json',
  197.                     'X-Shopify-Customer-Access-Token': customerAccessToken
  198.                 },
  199.                 body: JSON.stringify({
  200.                     customerId: customerId,
  201.                     patternData: patternData
  202.                 })
  203.             }).then(function(response) {
  204.                 if (!response.ok) {
  205.                     response.json().then(function(errorData) {
  206.                         reject(new Error(errorData.message || 'Failed to save to Shopify'));
  207.                     }).catch(function() {
  208.                         reject(new Error('Failed to save to Shopify'));
  209.                     });
  210.                     return;
  211.                 }
  212.  
  213.                 response.json().then(function(result) {
  214.                     console.log('โœ… Pattern saved to Shopify metafields:', result);
  215.                     resolve(result);
  216.                 }).catch(function(error) {
  217.                     reject(error);
  218.                 });
  219.                
  220.             }).catch(function(error) {
  221.                 console.error('โŒ Shopify save failed:', error);
  222.                 // Fallback to localStorage
  223.                 console.log('๐Ÿ”„ Falling back to localStorage...');
  224.                 saveToLocalStorage(patternData);
  225.                 reject(error);
  226.             });
  227.            
  228.         } catch (error) {
  229.             console.error('โŒ Shopify save failed:', error);
  230.             // Fallback to localStorage
  231.             console.log('๐Ÿ”„ Falling back to localStorage...');
  232.             saveToLocalStorage(patternData);
  233.             reject(error);
  234.         }
  235.     });
  236. }
  237.  
  238. // Save to localStorage as fallback
  239. function saveToLocalStorage(patternData) {
  240.     const existingPatterns = JSON.parse(localStorage.getItem('colorFlex_saved_patterns') || '[]');
  241.     existingPatterns.push(patternData);
  242.    
  243.     // Limit to last 20 patterns
  244.     const limitedPatterns = existingPatterns.slice(-20);
  245.     localStorage.setItem('colorFlex_saved_patterns', JSON.stringify(limitedPatterns));
  246. }
  247.  
  248. // Helper functions
  249. function getShopifyMetafield(key) {
  250.     // In a real Shopify app, this would fetch from customer metafields
  251.     return JSON.parse(localStorage.getItem('colorFlex_saved_patterns') || '[]');
  252. }
  253.  
  254. function getCustomerId() {
  255.     // Get from Shopify customer object or URL params
  256.     if (window.ShopifyCustomer && window.ShopifyCustomer.id) {
  257.         return window.ShopifyCustomer.id;
  258.     }
  259.    
  260.     // Check for Liquid template customer ID
  261.     if (typeof window.customer !== 'undefined' && window.customer.id) {
  262.         return window.customer.id;
  263.     }
  264.    
  265.     // Fallback to localStorage for development
  266.     return localStorage.getItem('development_customer_id') || null;
  267. }
  268.  
  269. function getCustomerAccessToken() {
  270.     // Get from Shopify customer access token
  271.     if (window.ShopifyCustomer && window.ShopifyCustomer.access_token) {
  272.         return window.ShopifyCustomer.access_token;
  273.     }
  274.    
  275.     // Check for global customer access token
  276.     if (window.customerAccessToken) {
  277.         return window.customerAccessToken;
  278.     }
  279.    
  280.     // Fallback for development
  281.     return localStorage.getItem('development_customer_token') || null;
  282. }
  283.  
  284. function showSaveNotification(message) {
  285.     // Create notification element
  286.     const notification = document.createElement('div');
  287.     notification.style.cssText = `
  288.         position: fixed;
  289.         top: 20px;
  290.         right: 20px;
  291.         background: ${message.includes('โœ…') ? '#48bb78' : '#f56565'};
  292.         color: white;
  293.         padding: 12px 20px;
  294.         border-radius: 8px;
  295.         font-family: 'Special Elite', monospace;
  296.         font-size: 14px;
  297.         font-weight: bold;
  298.         z-index: 10000;
  299.         box-shadow: 0 4px 12px rgba(0,0,0,0.3);
  300.         animation: slideIn 0.3s ease-out;
  301.     `;
  302.     notification.textContent = message;
  303.    
  304.     // Add CSS animation
  305.     const style = document.createElement('style');
  306.     style.textContent = `
  307.         @keyframes slideIn {
  308.             from { transform: translateX(100%); opacity: 0; }
  309.             to { transform: translateX(0); opacity: 1; }
  310.         }
  311.     `;
  312.     document.head.appendChild(style);
  313.    
  314.     document.body.appendChild(notification);
  315.    
  316.     // Remove after 3 seconds
  317.     setTimeout(() => {
  318.         notification.remove();
  319.         style.remove();
  320.     }, 3000);
  321. }
  322.  
  323. // Add save button to pattern preview
  324. function addSaveButton() {
  325.     // Check if button already exists
  326.     if (document.getElementById('saveToListBtn')) {
  327.         return;
  328.     }
  329.    
  330.     // Find pattern preview container
  331.     const patternPreview = document.getElementById('patternPreview') || document.querySelector('#patternPreviewWrapper');
  332.     if (!patternPreview) {
  333.         console.warn('โš ๏ธ Pattern preview container not found for save button');
  334.         return;
  335.     }
  336.    
  337.     // Create save button
  338.     const saveButton = document.createElement('button');
  339.     saveButton.id = 'saveToListBtn';
  340.     saveButton.innerHTML = '๐Ÿ’พ Save to My List';
  341.     saveButton.style.cssText = `
  342.         position: absolute;
  343.         top: 10px;
  344.         right: 10px;
  345.         background: linear-gradient(135deg, #48bb78 0%, #38a169 100%);
  346.         color: white;
  347.         border: none;
  348.         padding: 10px 16px;
  349.         border-radius: 20px;
  350.         font-family: 'Special Elite', monospace;
  351.         font-size: 12px;
  352.         font-weight: bold;
  353.         cursor: pointer;
  354.         box-shadow: 0 3px 10px rgba(0,0,0,0.2);
  355.         transition: all 0.3s ease;
  356.         z-index: 100;
  357.     `;
  358.    
  359.     // Add hover effect
  360.     saveButton.addEventListener('mouseenter', () => {
  361.         saveButton.style.transform = 'translateY(-2px)';
  362.         saveButton.style.boxShadow = '0 5px 15px rgba(0,0,0,0.3)';
  363.     });
  364.    
  365.     saveButton.addEventListener('mouseleave', () => {
  366.         saveButton.style.transform = 'translateY(0)';
  367.         saveButton.style.boxShadow = '0 3px 10px rgba(0,0,0,0.2)';
  368.     });
  369.    
  370.     // Add click handler
  371.     saveButton.addEventListener('click', saveToMyList);
  372.    
  373.     // Create "View Saved" button
  374.     const viewSavedButton = document.createElement('button');
  375.     viewSavedButton.id = 'viewSavedBtn';
  376.     viewSavedButton.innerHTML = '๐Ÿ“‚ View Saved';
  377.     viewSavedButton.style.cssText = `
  378.         position: absolute;
  379.         top: 50px;
  380.         right: 10px;
  381.         background: linear-gradient(135deg, #2d3748 0%, #1a202c 100%);
  382.         color: white;
  383.         border: none;
  384.         padding: 10px 16px;
  385.         border-radius: 20px;
  386.         font-family: 'Special Elite', monospace;
  387.         font-size: 12px;
  388.         font-weight: bold;
  389.         cursor: pointer;
  390.         box-shadow: 0 3px 10px rgba(0,0,0,0.2);
  391.         transition: all 0.3s ease;
  392.         z-index: 100;
  393.     `;
  394.    
  395.     // Add hover effect for view button
  396.     viewSavedButton.addEventListener('mouseenter', function() {
  397.         viewSavedButton.style.transform = 'translateY(-2px)';
  398.         viewSavedButton.style.boxShadow = '0 5px 15px rgba(0,0,0,0.3)';
  399.     });
  400.    
  401.     viewSavedButton.addEventListener('mouseleave', function() {
  402.         viewSavedButton.style.transform = 'translateY(0)';
  403.         viewSavedButton.style.boxShadow = '0 3px 10px rgba(0,0,0,0.2)';
  404.     });
  405.    
  406.     // Add click handler for view saved patterns
  407.     viewSavedButton.addEventListener('click', showSavedPatternsModal);
  408.    
  409.     // Add to pattern preview container
  410.     patternPreview.style.position = 'relative'; // Ensure relative positioning
  411.     patternPreview.appendChild(saveButton);
  412.     patternPreview.appendChild(viewSavedButton);
  413.    
  414.     console.log('โœ… Save and view buttons added to pattern preview');
  415. }
  416.  
  417. // Show saved patterns modal
  418. function showSavedPatternsModal() {
  419.     try {
  420.         console.log('๐Ÿ” Loading saved patterns...');
  421.        
  422.         // Get saved patterns from localStorage (will add Shopify support later)
  423.         var savedPatterns = JSON.parse(localStorage.getItem('colorFlex_saved_patterns') || '[]');
  424.         console.log('๐Ÿ“ฑ Loaded patterns from localStorage:', savedPatterns.length);
  425.        
  426.         createSavedPatternsModal(savedPatterns);
  427.        
  428.     } catch (error) {
  429.         console.error('โŒ Error loading saved patterns:', error);
  430.         showSaveNotification('โŒ Failed to load saved patterns');
  431.     }
  432. }
  433.  
  434. // Create saved patterns modal
  435. function createSavedPatternsModal(patterns) {
  436.     // Remove existing modal
  437.     var existingModal = document.getElementById('savedPatternsModal');
  438.     if (existingModal) {
  439.         existingModal.remove();
  440.     }
  441.    
  442.     // Create modal overlay
  443.     var modal = document.createElement('div');
  444.     modal.id = 'savedPatternsModal';
  445.     modal.style.cssText = `
  446.         position: fixed;
  447.         top: 0;
  448.         left: 0;
  449.         width: 100%;
  450.         height: 100%;
  451.         background: rgba(0,0,0,0.8);
  452.         z-index: 10000;
  453.         display: flex;
  454.         justify-content: center;
  455.         align-items: center;
  456.     `;
  457.    
  458.     // Create modal content
  459.     var modalContent = document.createElement('div');
  460.     modalContent.style.cssText = `
  461.         background: #1a202c;
  462.         color: white;
  463.         padding: 20px;
  464.         border-radius: 10px;
  465.         max-width: 600px;
  466.         max-height: 80vh;
  467.         overflow-y: auto;
  468.         font-family: 'Special Elite', monospace;
  469.         border: 2px solid #4a5568;
  470.     `;
  471.    
  472.     // Modal header
  473.     var header = document.createElement('div');
  474.     header.style.cssText = 'display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; border-bottom: 1px solid #4a5568; padding-bottom: 10px;';
  475.    
  476.     var title = document.createElement('h2');
  477.     title.textContent = '๐Ÿ“‚ My Saved Patterns (' + patterns.length + ')';
  478.     title.style.margin = '0';
  479.     title.style.color = '#f0e6d2';
  480.    
  481.     var closeBtn = document.createElement('button');
  482.     closeBtn.textContent = 'ร—';
  483.     closeBtn.style.cssText = `
  484.         background: none;
  485.         border: none;
  486.         color: white;
  487.         font-size: 24px;
  488.         cursor: pointer;
  489.         padding: 0;
  490.         width: 30px;
  491.         height: 30px;
  492.         border-radius: 50%;
  493.         background: #f56565;
  494.     `;
  495.     closeBtn.addEventListener('click', function() { modal.remove(); });
  496.    
  497.     header.appendChild(title);
  498.     header.appendChild(closeBtn);
  499.     modalContent.appendChild(header);
  500.    
  501.     // Patterns list
  502.     if (patterns.length === 0) {
  503.         var emptyMessage = document.createElement('div');
  504.         emptyMessage.innerHTML = `
  505.             <div style="text-align: center; padding: 40px; color: #a0aec0;">
  506.                 <div style="font-size: 48px; margin-bottom: 20px;">๐ŸŽจ</div>
  507.                 <h3>No saved patterns yet</h3>
  508.                 <p>Start customizing patterns and save your favorites!</p>
  509.             </div>
  510.         `;
  511.         modalContent.appendChild(emptyMessage);
  512.     } else {
  513.         for (var i = 0; i < patterns.length; i++) {
  514.             var patternDiv = createSavedPatternItem(patterns[i], i);
  515.             modalContent.appendChild(patternDiv);
  516.         }
  517.     }
  518.    
  519.     modal.appendChild(modalContent);
  520.     document.body.appendChild(modal);
  521.    
  522.     // Close on overlay click
  523.     modal.addEventListener('click', function(e) {
  524.         if (e.target === modal) {
  525.             modal.remove();
  526.         }
  527.     });
  528. }
  529.  
  530. // Create individual saved pattern item
  531. function createSavedPatternItem(pattern, index) {
  532.     var item = document.createElement('div');
  533.     item.style.cssText = `
  534.         border: 1px solid #4a5568;
  535.         border-radius: 8px;
  536.         padding: 15px;
  537.         margin-bottom: 10px;
  538.         background: #2d3748;
  539.         transition: background 0.3s ease;
  540.     `;
  541.    
  542.     // Hover effect
  543.     item.addEventListener('mouseenter', function() {
  544.         item.style.background = '#374151';
  545.     });
  546.     item.addEventListener('mouseleave', function() {
  547.         item.style.background = '#2d3748';
  548.     });
  549.    
  550.     var info = document.createElement('div');
  551.     info.innerHTML = `
  552.         <div style="display: flex; justify-content: space-between; align-items: start; margin-bottom: 10px;">
  553.             <div>
  554.                 <strong style="color: #f0e6d2; font-size: 16px;">๐ŸŽจ ${pattern.pattern.name}</strong><br>
  555.                 <small style="color: #a0aec0;">๐Ÿ“ Collection: ${pattern.pattern.collection}</small><br>
  556.                 <small style="color: #a0aec0;">๐Ÿ“… Saved: ${new Date(pattern.timestamp).toLocaleDateString()}</small><br>
  557.                 <small style="color: #a0aec0;">๐ŸŽฏ Layers: ${pattern.pattern.layers.length}</small>
  558.             </div>
  559.             <div style="font-size: 12px; color: #68d391; background: #22543d; padding: 4px 8px; border-radius: 12px;">
  560.                 ID: ${pattern.id}
  561.             </div>
  562.         </div>
  563.     `;
  564.    
  565.     // Show layer colors
  566.     if (pattern.pattern.layers && pattern.pattern.layers.length > 0) {
  567.         var layersDiv = document.createElement('div');
  568.         layersDiv.style.cssText = 'margin: 10px 0; padding: 8px; background: #1a202c; border-radius: 4px; border-left: 3px solid #4299e1;';
  569.         layersDiv.innerHTML = '<small style="color: #4299e1; font-weight: bold;">Layer Colors:</small><br>';
  570.        
  571.         for (var i = 0; i < pattern.pattern.layers.length; i++) {
  572.             var layer = pattern.pattern.layers[i];
  573.             layersDiv.innerHTML += '<small style="color: #e2e8f0;">โ€ข ' + layer.label + ': <span style="color: ' + layer.color + '; font-weight: bold;">' + layer.color + '</span></small><br>';
  574.         }
  575.        
  576.         info.appendChild(layersDiv);
  577.     }
  578.    
  579.     var buttons = document.createElement('div');
  580.     buttons.style.cssText = 'margin-top: 15px; display: flex; gap: 10px; justify-content: flex-end;';
  581.    
  582.     // Delete button
  583.     var deleteBtn = document.createElement('button');
  584.     deleteBtn.textContent = '๐Ÿ—‘๏ธ Delete';
  585.     deleteBtn.style.cssText = `
  586.         background: #f56565;
  587.         color: white;
  588.         border: none;
  589.         padding: 8px 12px;
  590.         border-radius: 4px;
  591.         cursor: pointer;
  592.         font-size: 12px;
  593.         font-family: 'Special Elite', monospace;
  594.         transition: background 0.3s ease;
  595.     `;
  596.     deleteBtn.addEventListener('mouseenter', function() {
  597.         deleteBtn.style.background = '#e53e3e';
  598.     });
  599.     deleteBtn.addEventListener('mouseleave', function() {
  600.         deleteBtn.style.background = '#f56565';
  601.     });
  602.     deleteBtn.addEventListener('click', function() {
  603.         if (confirm('๐Ÿ—‘๏ธ Delete "' + pattern.pattern.name + '"?\n\nThis action cannot be undone.')) {
  604.             deleteSavedPattern(pattern.id);
  605.             document.getElementById('savedPatternsModal').remove();
  606.             showSavedPatternsModal(); // Refresh modal
  607.         }
  608.     });
  609.    
  610.     buttons.appendChild(deleteBtn);
  611.    
  612.     item.appendChild(info);
  613.     item.appendChild(buttons);
  614.    
  615.     return item;
  616. }
  617.  
  618. // Delete a saved pattern
  619. function deleteSavedPattern(patternId) {
  620.     try {
  621.         // Delete from localStorage
  622.         var patterns = JSON.parse(localStorage.getItem('colorFlex_saved_patterns') || '[]');
  623.         var updatedPatterns = patterns.filter(function(p) { return p.id !== patternId; });
  624.         localStorage.setItem('colorFlex_saved_patterns', JSON.stringify(updatedPatterns));
  625.        
  626.         console.log('โœ… Pattern deleted from localStorage');
  627.         showSaveNotification('โœ… Pattern deleted successfully!');
  628.        
  629.     } catch (error) {
  630.         console.error('โŒ Error deleting pattern:', error);
  631.         showSaveNotification('โŒ Failed to delete pattern');
  632.     }
  633. }
  634.  
  635. // Path normalization function to fix ./data/ vs data/ inconsistencies
  636. function normalizePath(path) {
  637.     if (!path || typeof path !== 'string') return path;
  638.    
  639.     // If it's already a full URL, return as-is
  640.     if (path.startsWith('http://') || path.startsWith('https://')) {
  641.         return path;
  642.     }
  643.    
  644.     // Convert "./data/" to "data/" for consistency
  645.     if (path.startsWith('./data/')) {
  646.         path = path.substring(2); // Remove the "./"
  647.     }
  648.    
  649.     // For any other relative paths, ensure they don't start with "./"
  650.     if (path.startsWith('./')) {
  651.         path = path.substring(2);
  652.     }
  653.    
  654.     // If it's a data/ path, convert to absolute URL
  655.     if (path.startsWith('data/')) {
  656.         return `https://so-animation.com/colorflex/${path}`;
  657.     }
  658.    
  659.     return path;
  660. }
  661.  
  662. // Store furniture view settings globally for consistency
  663. const furnitureViewSettings = {
  664.     scale: 0.7,
  665.     offsetX: 0,
  666.     offsetY: -120,
  667.     // Zoom states
  668.     isZoomed: false,
  669.     zoomScale: 2,  // 220% zoom when clicked
  670.     zoomX: 0,        // Where we're zoomed to
  671.     zoomY: 0         // Where we're zoomed to
  672.  
  673. };
  674. const DEFAULT_FURNITURE_SETTINGS = {
  675.     scale: 0.7,
  676.     offsetX: 0,
  677.     offsetY: -120
  678. };
  679.  
  680.  
  681. function addInteractiveZoom() {
  682.     console.log("๐Ÿ” Adding interactive zoom to furniture preview");
  683.    
  684.     const roomMockup = document.getElementById('roomMockup');
  685.     if (!roomMockup) {
  686.         console.error("โŒ Room mockup container not found");
  687.         return;
  688.     }
  689.    
  690.     // โœ… Add debouncing to prevent rapid clicks
  691.     let isZoomInProgress = false;
  692.     let lastClickTime = 0;
  693.     const MIN_CLICK_INTERVAL = 500; // Minimum 500ms between clicks
  694.    
  695.     roomMockup.style.cursor = 'pointer';
  696.     roomMockup.onclick = null;
  697.    
  698.     roomMockup.addEventListener('click', function(e) {
  699.         const currentTime = Date.now();
  700.        
  701.         // โœ… Debounce rapid clicks
  702.         if (currentTime - lastClickTime < MIN_CLICK_INTERVAL) {
  703.             console.log("๐Ÿšซ Click ignored - too rapid");
  704.             return;
  705.         }
  706.        
  707.         // โœ… Prevent overlapping zoom operations
  708.         if (isZoomInProgress) {
  709.             console.log("๐Ÿšซ Click ignored - zoom in progress");
  710.             return;
  711.         }
  712.        
  713.         lastClickTime = currentTime;
  714.         isZoomInProgress = true;
  715.        
  716.         console.log("๐Ÿ–ฑ๏ธ Room mockup clicked (debounced)");
  717.        
  718.         const isFurnitureCollection = appState.selectedCollection?.wallMask != null;
  719.         if (!isFurnitureCollection) {
  720.             console.log("Not a furniture collection, ignoring click");
  721.             isZoomInProgress = false;
  722.             return;
  723.         }
  724.        
  725.         // Get click position
  726.         const rect = roomMockup.getBoundingClientRect();
  727.         const x = e.clientX - rect.left;
  728.         const y = e.clientY - rect.top;
  729.         const clickX = (x / rect.width) * 600;
  730.         const clickY = (y / rect.height) * 450;
  731.        
  732.         console.log(`๐ŸŽฏ Click at canvas coordinates: (${clickX.toFixed(0)}, ${clickY.toFixed(0)})`);
  733.        
  734.         // โœ… More robust state detection
  735.         const currentScale = furnitureViewSettings.scale;
  736.         const isCurrentlyZoomed = currentScale > 1.0; // Any scale > 1.0 is considered "zoomed"
  737.        
  738.         console.log(`๐Ÿ” Current state - scale: ${currentScale}, considered zoomed: ${isCurrentlyZoomed}`);
  739.        
  740.         if (isCurrentlyZoomed) {
  741.             // Zoom out to default
  742.             console.log(`๐Ÿ” Zooming out to default scale (${DEFAULT_FURNITURE_SETTINGS.scale})`);
  743.             furnitureViewSettings.isZoomed = false;
  744.             furnitureViewSettings.scale = DEFAULT_FURNITURE_SETTINGS.scale;
  745.             furnitureViewSettings.offsetX = DEFAULT_FURNITURE_SETTINGS.offsetX;
  746.             furnitureViewSettings.offsetY = DEFAULT_FURNITURE_SETTINGS.offsetY;
  747.             roomMockup.style.cursor = 'zoom-in';
  748.         } else {
  749.             // Zoom in to click point
  750.             console.log(`๐Ÿ” Zooming in to click point`);
  751.             furnitureViewSettings.isZoomed = true;
  752.             furnitureViewSettings.scale = furnitureViewSettings.zoomScale; // 2.2
  753.            
  754.             // Proper offset calculation accounting for default offset
  755.             const canvasWidth = 600;
  756.             const canvasHeight = 450;
  757.             const centerX = canvasWidth / 2;
  758.             const centerY = canvasHeight / 2;
  759.            
  760.             // Calculate how much to offset to center the clicked point
  761.             const defaultScale = 0.7;  // Your default scale
  762.             const defaultOffsetX = 0;  // Your default offsetX  
  763.             const defaultOffsetY = -120; // Your default offsetY
  764.            
  765.             const scaleFactor = furnitureViewSettings.zoomScale / defaultScale; // 2.2 / 0.7 = 3.14
  766.            
  767.             // Calculate offset relative to the default position
  768.             furnitureViewSettings.offsetX = defaultOffsetX + (centerX - clickX) * (scaleFactor - 1);
  769.             furnitureViewSettings.offsetY = defaultOffsetY + (centerY - clickY) * (scaleFactor - 1);
  770.            
  771.             console.log(`   Scale factor: ${scaleFactor}`);
  772.             console.log(`   Default offset: (${defaultOffsetX}, ${defaultOffsetY})`);
  773.             console.log(`   New offset: (${furnitureViewSettings.offsetX.toFixed(0)}, ${furnitureViewSettings.offsetY.toFixed(0)})`);
  774.            
  775.             roomMockup.style.cursor = 'zoom-out';
  776.         }
  777.        
  778.         console.log("๐Ÿ”„ Calling updateFurniturePreview with new zoom state");
  779.         console.log("๐Ÿ”„ Final settings:", JSON.stringify({
  780.             scale: furnitureViewSettings.scale,
  781.             offsetX: furnitureViewSettings.offsetX,
  782.             offsetY: furnitureViewSettings.offsetY,
  783.             isZoomed: furnitureViewSettings.isZoomed
  784.         }, null, 2));
  785.        
  786.         // โœ… Call update and reset progress flag when done
  787.         if (typeof updateFurniturePreview === 'function') {
  788.             updateFurniturePreview().then(() => {
  789.                 isZoomInProgress = false;
  790.                 console.log("โœ… Zoom operation completed");
  791.             }).catch(error => {
  792.                 console.error("โŒ Zoom operation failed:", error);
  793.                 isZoomInProgress = false;
  794.             });
  795.         } else {
  796.             console.error("โŒ updateFurniturePreview function not found!");
  797.             updateDisplays();
  798.             isZoomInProgress = false;
  799.         }
  800.     });
  801.    
  802.     // Set initial cursor
  803.     const isFurnitureCollection = window.appState.selectedCollection && window.appState.selectedCollection.wallMask != null;
  804.     if (isFurnitureCollection) {
  805.         const currentScale = furnitureViewSettings.scale;
  806.         const isCurrentlyZoomed = currentScale > 1.0;
  807.         roomMockup.style.cursor = isCurrentlyZoomed ? 'zoom-out' : 'zoom-in';
  808.         console.log("โœ… Set cursor for furniture collection");
  809.     } else {
  810.         roomMockup.style.cursor = 'default';
  811.         console.log("โœ… Set default cursor for non-furniture collection");
  812.     }
  813.    
  814.     console.log("โœ… Interactive zoom added to room mockup");
  815. }
  816.  
  817. // Also add this debug function to test zoom manually:
  818. function testZoom() {
  819.     console.log("๐Ÿงช Testing zoom functionality");
  820.     console.log("Current furnitureViewSettings:", furnitureViewSettings);
  821.    
  822.     // Test zoom in
  823.     furnitureViewSettings.isZoomed = true;
  824.     furnitureViewSettings.scale = 2.2;
  825.     furnitureViewSettings.offsetX = -100;
  826.     furnitureViewSettings.offsetY = -50;
  827.    
  828.     console.log("Updated furnitureViewSettings:", furnitureViewSettings);
  829.    
  830.     // Trigger re-render
  831.     if (typeof updateFurniturePreview === 'function') {
  832.         console.log("Calling updateFurniturePreview...");
  833.         updateFurniturePreview();
  834.     } else {
  835.         console.error("updateFurniturePreview function not found!");
  836.     }
  837. }
  838.  
  839.  
  840.  
  841. // DOM references
  842. const dom = {
  843.     patternName: document.getElementById("patternName"),
  844.     collectionHeader: document.getElementById("collectionHeader"),
  845.     collectionThumbnails: document.getElementById("collectionThumbnails"),
  846.     layerInputsContainer: document.getElementById("layerInputsContainer"),
  847.     curatedColorsContainer: document.getElementById("curatedColorsContainer"),
  848.     coordinatesContainer: document.getElementById("coordinatesContainer"),
  849.     preview: document.getElementById("preview"),
  850.     roomMockup: document.getElementById("roomMockup"),
  851.     printButton: document.getElementById("printButton") // Assuming a button exists
  852. };
  853.  
  854. // Validate DOM elements and report missing ones
  855. function validateDOMElements() {
  856.     console.log("๐Ÿ” DOM Validation:");
  857.     Object.entries(dom).forEach(([key, element]) => {
  858.         if (element) {
  859.             console.log(`  โœ… ${key}: found`);
  860.         } else {
  861.             console.error(`  โŒ ${key}: NOT FOUND - missing element with id "${key}"`);
  862.         }
  863.     });
  864. }
  865.  
  866. // Watch changes to patternName
  867. const patternNameElement = document.getElementById("patternName");
  868. Object.defineProperty(dom, 'patternName', {
  869.     get() {
  870.         return patternNameElement;
  871.     },
  872.     set(value) {
  873.         console.log("Setting #patternName to:", value, "Caller:", new Error().stack.split('\n')[2].trim());
  874.         patternNameElement.textContent = value;
  875.     },
  876.     configurable: true
  877. });
  878.  
  879. // Debug function to check what's happening with collection names
  880. window.debugCollectionName = function() {
  881.     console.log(`๐Ÿ” COLLECTION NAME DEBUG:`);
  882.     console.log(`========================`);
  883.     console.log(`Current collection name: "${appState.selectedCollection?.name}"`);
  884.     console.log(`Current pattern name: "${appState.currentPattern?.name}"`);
  885.     console.log(`Furniture mode: ${appState.furnitureMode}`);
  886.    
  887.     if (appState.furnitureMode) {
  888.         console.log(`Original collection: "${appState.originalCollection?.name}"`);
  889.         console.log(`Original collection exists: ${!!appState.originalCollection?.fullCollection}`);
  890.        
  891.         // Check if we can get the original collection name from the furniture collection
  892.         const originalFromFurniture = appState.selectedCollection?.originalCollectionName;
  893.         console.log(`Original collection from furniture collection: "${originalFromFurniture}"`);
  894.     }
  895.    
  896.     // Test what the path should be
  897.     if (appState.selectedCollection && appState.currentPattern) {
  898.         let collectionNameForPaths;
  899.        
  900.         if (appState.furnitureMode) {
  901.             // Try multiple ways to get the original collection name
  902.             collectionNameForPaths = appState.originalCollection?.name
  903.                 || appState.selectedCollection?.originalCollectionName
  904.                 || "UNKNOWN";
  905.         } else {
  906.             collectionNameForPaths = appState.selectedCollection.name;
  907.         }
  908.        
  909.         const patternName = appState.currentPattern.name;
  910.         const slug = createPatternSlug(patternName);
  911.        
  912.         console.log(`Expected path structure:`);
  913.         console.log(`  Collection for paths: "${collectionNameForPaths}"`);
  914.         console.log(`  Pattern: "${patternName}"`);
  915.         console.log(`  Slug: "${slug}"`);
  916.         console.log(`  Should be: data/furniture/sofa-capitol/patterns/${collectionNameForPaths}/${slug}/`);
  917.        
  918.         if (collectionNameForPaths === "UNKNOWN") {
  919.             console.error(`โŒ Cannot determine original collection name!`);
  920.             console.error(`   This is why paths are broken.`);
  921.         }
  922.     }
  923.    
  924.     return {
  925.         selectedCollection: appState.selectedCollection?.name,
  926.         currentPattern: appState.currentPattern?.name,
  927.         furnitureMode: appState.furnitureMode,
  928.         originalCollection: appState.originalCollection?.name
  929.     };
  930. };
  931. window.getAppState = function() {
  932.     return {
  933.         selectedCollection: appState.selectedCollection?.name,
  934.         currentPattern: appState.currentPattern?.name,
  935.         furnitureMode: appState.furnitureMode,
  936.         originalCollection: appState.originalCollection?.name,
  937.         collections: appState.collections?.map(c => c.name),
  938.         furnitureConfigLoaded: !!furnitureConfig
  939.     };
  940. };
  941. window.fixOriginalCollection = function(originalCollectionName) {
  942.     console.log(`๐Ÿ”ง QUICK FIX: Setting original collection to "${originalCollectionName}"`);
  943.    
  944.     if (!appState.originalCollection) {
  945.         appState.originalCollection = {};
  946.     }
  947.    
  948.     appState.originalCollection.name = originalCollectionName;
  949.    
  950.     // Also store it in the furniture collection for future reference
  951.     if (appState.selectedCollection) {
  952.         appState.selectedCollection.originalCollectionName = originalCollectionName;
  953.     }
  954.    
  955.     console.log(`โœ… Fixed! Original collection name is now: "${appState.originalCollection.name}"`);
  956.     console.log(`Run debugCollectionName() to verify the fix.`);
  957.    
  958.     return {
  959.         originalCollection: appState.originalCollection.name,
  960.         furnitureCollection: appState.selectedCollection?.originalCollectionName
  961.     };
  962. };
  963.  
  964. // Status check accessible from console
  965. window.checkStatus = function() {
  966.     console.log(`๐Ÿ” FURNITURE IMPLEMENTATION STATUS CHECK:`);
  967.     console.log(`======================================`);
  968.    
  969.     // Check if furniture config is loaded
  970.     if (!furnitureConfig) {
  971.         console.log(`โŒ furnitureConfig not loaded`);
  972.         return { error: "furnitureConfig not loaded" };
  973.     }
  974.     console.log(`โœ… furnitureConfig loaded: ${Object.keys(furnitureConfig).length} furniture pieces`);
  975.    
  976.     // Check collections
  977.     if (!appState.collections || appState.collections.length === 0) {
  978.         console.log(`โŒ Collections not loaded`);
  979.         return { error: "Collections not loaded" };
  980.     }
  981.     console.log(`โœ… Collections loaded: ${appState.collections.length} collections`);
  982.    
  983.     // Check current state
  984.     const currentCollection = appState.selectedCollection?.name;
  985.     if (!currentCollection) {
  986.         console.log(`โŒ No collection currently selected`);
  987.         return { error: "No collection selected" };
  988.     }
  989.     console.log(`โœ… Current collection: ${currentCollection}`);
  990.    
  991.     // Check compatibility
  992.     const compatible = getCompatibleFurniture(currentCollection);
  993.     console.log(`โœ… Compatible furniture: ${compatible.length} pieces`);
  994.     compatible.forEach(f => console.log(`   - ${f.name}`));
  995.    
  996.     // Check if Try Furniture button should be visible
  997.     const tryButton = document.getElementById('tryFurnitureBtn');
  998.     const backButton = document.getElementById('backToPatternsBtn');
  999.    
  1000.     if (appState.furnitureMode) {
  1001.         console.log(`๐Ÿช‘ Currently in FURNITURE MODE`);
  1002.         console.log(`   Back button present: ${!!backButton}`);
  1003.     } else {
  1004.         console.log(`๐ŸŽจ Currently in PATTERN MODE`);
  1005.         console.log(`   Try Furniture button present: ${!!tryButton}`);
  1006.         if (!tryButton && compatible.length > 0) {
  1007.             console.log(`โš ๏ธ  Try Furniture button should be visible but isn't!`);
  1008.        }
  1009.    }
  1010.    
  1011.    return {
  1012.        furnitureConfigLoaded: !!furnitureConfig,
  1013.        collectionsLoaded: appState.collections?.length > 0,
  1014.        currentCollection: currentCollection,
  1015.        compatibleFurniture: compatible.length,
  1016.        furnitureMode: appState.furnitureMode,
  1017.        tryButtonPresent: !!tryButton,
  1018.        backButtonPresent: !!backButton,
  1019.        originalCollection: appState.originalCollection?.name
  1020.    };
  1021. };
  1022.  
  1023. function ensureButtonsAfterUpdate() {
  1024.    // Small delay to ensure DOM update is complete
  1025.    setTimeout(() => {
  1026.        if (!appState.furnitureMode && !document.getElementById('tryFurnitureBtn')) {
  1027.            if (window.COLORFLEX_DEBUG) {
  1028.                console.log("๐Ÿ”„ Re-adding Try Fabric button after room mockup update");
  1029.            }
  1030.            addTryFurnitureButton();
  1031.        }
  1032.        
  1033.        if (appState.furnitureMode && !document.getElementById('backToPatternsBtn')) {
  1034.            if (window.COLORFLEX_DEBUG) {
  1035.                console.log("๐Ÿ”„ Re-adding Back to Patterns button after room mockup update");
  1036.            }
  1037.            addBackToPatternsButton();
  1038.        }
  1039.    }, 50);
  1040. }
  1041.  
  1042. // Test pattern slug generation
  1043. window.testSlug = function(patternName) {
  1044.    const slug = createPatternSlug(patternName);
  1045.    console.log(`Pattern: "${patternName}" โ†’ Slug: "${slug}"`);
  1046.    return slug;
  1047. };
  1048.  
  1049. // Simple state viewer
  1050. window.viewState = function() {
  1051.    const state = {
  1052.        selectedCollection: appState.selectedCollection?.name,
  1053.        currentPattern: appState.currentPattern?.name,
  1054.        furnitureMode: appState.furnitureMode,
  1055.        originalCollection: appState.originalCollection?.name,
  1056.        patterns: appState.selectedCollection?.patterns?.length,
  1057.        furnitureConfig: Object.keys(furnitureConfig || {})
  1058.    };
  1059.    
  1060.    console.table(state);
  1061.    return state;
  1062. };
  1063.  
  1064. // Debug functions available in development mode only
  1065. if (window.location.hostname === 'localhost' || window.location.hostname.includes('dev')) {
  1066.    console.log(`
  1067. ๐Ÿ”ง DEBUG FUNCTIONS LOADED!
  1068. =========================
  1069.  
  1070. Available console commands:
  1071. โ€ข debugCollectionName() - Debug collection name issues
  1072. โ€ข fixOriginalCollection("botanicals") - Quick fix for collection name
  1073. โ€ข checkStatus() - Check implementation status  
  1074. โ€ข viewState() - View current app state
  1075. โ€ข testSlug("Pattern Name") - Test slug conversion
  1076. โ€ข getAppState() - Get simplified app state
  1077.  
  1078. Try running: debugCollectionName()
  1079. `);
  1080. }
  1081.  
  1082. // Create pattern slug from pattern name
  1083. function createPatternSlug(patternName) {
  1084.    return patternName
  1085.        .toLowerCase()
  1086.        .replace(/[^a-z0-9\s-]/g, '') // Remove special characters
  1087.        .replace(/\s+/g, '-')         // Replace spaces with hyphens
  1088.        .replace(/-+/g, '-')          // Remove multiple consecutive hyphens
  1089.        .replace(/^-|-$/g, '')        // Remove leading/trailing hyphens
  1090.        .trim();
  1091. }
  1092.  
  1093. window.simpleDebug = function() {
  1094.    console.log(`๐Ÿ” SIMPLE DEBUG:`);
  1095.    console.log(`================`);
  1096.    
  1097.    if (appState.furnitureMode) {
  1098.        console.log(`In furniture mode: YES`);
  1099.        console.log(`Current collection: "${appState.selectedCollection?.name}"`);
  1100.        console.log(`Stored original collection: "${appState.selectedCollection?.originalCollectionName}"`);
  1101.        console.log(`Current pattern: "${appState.currentPattern?.name}"`);
  1102.        
  1103.        if (appState.selectedCollection?.originalCollectionName) {
  1104.            const slug = createPatternSlug(appState.currentPattern?.name || "test");
  1105.            console.log(`โœ… Path should be: data/furniture/sofa-capitol/patterns/${appState.selectedCollection.originalCollectionName}/${slug}/`);
  1106.        } else {
  1107.            console.log(`โŒ No original collection name stored!`);
  1108.        }
  1109.    } else {
  1110.        console.log(`In furniture mode: NO`);
  1111.        console.log(`Current collection: "${appState.selectedCollection?.name}"`);
  1112.    }
  1113. };
  1114.  
  1115. // Quick fix function:
  1116. window.quickFix = function() {
  1117.    if (appState.furnitureMode && !appState.selectedCollection?.originalCollectionName) {
  1118.        // Try to guess the original collection from the furniture collection name
  1119.        const furnitureCollectionName = appState.selectedCollection?.name;
  1120.        if (furnitureCollectionName && furnitureCollectionName.includes("BOTANICAL")) {
  1121.            appState.selectedCollection.originalCollectionName = "botanicals";
  1122.            console.log(`๐Ÿ”ง Quick fix: Set original collection to "botanicals"`);
  1123.            return true;
  1124.        }
  1125.    }
  1126.    return false;
  1127. };
  1128.  
  1129. window.fixPatternPaths = function() {
  1130.    if (appState.furnitureMode && appState.currentPattern) {
  1131.        const originalCollectionName = appState.selectedCollection.originalCollectionName;
  1132.        const furnitureConfig = appState.selectedCollection.furnitureConfig;
  1133.        
  1134.        console.log(`๐Ÿ”ง Regenerating pattern paths:`);
  1135.        console.log(`   Collection: ${originalCollectionName}`);
  1136.        console.log(`   Pattern: ${appState.currentPattern.name}`);
  1137.        
  1138.        // Re-create the furniture pattern with correct paths
  1139.        const correctedPattern = createFurniturePattern(
  1140.            appState.currentPattern.originalPattern || appState.currentPattern,
  1141.            furnitureConfig,
  1142.            originalCollectionName
  1143.        );
  1144.        
  1145.        // Update the current pattern
  1146.        appState.currentPattern = correctedPattern;
  1147.        
  1148.        // Update in the collection too
  1149.        const patternIndex = appState.selectedCollection.patterns.findIndex(p => p.id === correctedPattern.id);
  1150.        if (patternIndex !== -1) {
  1151.            appState.selectedCollection.patterns[patternIndex] = correctedPattern;
  1152.        }
  1153.        
  1154.        console.log(`โœ… Pattern paths regenerated`);
  1155.        return correctedPattern;
  1156.    }
  1157. };
  1158.  
  1159.  
  1160. // Cache for furniture compatibility checks to improve performance
  1161. let furnitureCompatibilityCache = new Map();
  1162. let addFurnitureButtonDebounce = null;
  1163.  
  1164. function getCompatibleFurniture(collectionName) {
  1165.    // Check cache first to avoid repeated computations
  1166.    if (furnitureCompatibilityCache.has(collectionName)) {
  1167.        return furnitureCompatibilityCache.get(collectionName);
  1168.    }
  1169.    
  1170.    // Reduced logging for performance
  1171.    if (window.COLORFLEX_DEBUG) {
  1172.        console.log(`๐Ÿช‘ Checking furniture compatibility for collection: ${collectionName}`);
  1173.    }
  1174.    
  1175.    if (!furnitureConfig) {
  1176.        // Don't spam the console - only warn once per collection
  1177.         if (!furnitureCompatibilityCache.has(collectionName + '_warned')) {
  1178.             console.warn("Furniture config not loaded yet");
  1179.             furnitureCompatibilityCache.set(collectionName + '_warned', true);
  1180.         }
  1181.         return [];
  1182.     }
  1183.    
  1184.     const compatible = Object.entries(furnitureConfig)
  1185.         .filter(([furnitureId, config]) => {
  1186.             const isCompatible = config.compatibleCollections &&
  1187.                                config.compatibleCollections.includes(collectionName);
  1188.             return isCompatible;
  1189.         })
  1190.         .map(([furnitureId, config]) => ({
  1191.             id: furnitureId,
  1192.             name: config.name,
  1193.             thumbnail: config.thumbnail,
  1194.             description: config.description || '',
  1195.             config: config
  1196.         }));
  1197.    
  1198.     // Cache the result for future use
  1199.     furnitureCompatibilityCache.set(collectionName, compatible);
  1200.    
  1201.     if (window.COLORFLEX_DEBUG) {
  1202.         console.log(`Found ${compatible.length} compatible furniture pieces`);
  1203.     }
  1204.     return compatible;
  1205. }
  1206.  
  1207. function addTryFurnitureButtonDebounced() {
  1208.     // Debounce to prevent excessive calls
  1209.     if (addFurnitureButtonDebounce) {
  1210.         clearTimeout(addFurnitureButtonDebounce);
  1211.     }
  1212.    
  1213.     addFurnitureButtonDebounce = setTimeout(() => {
  1214.         addTryFurnitureButtonInternal();
  1215.     }, 100); // 100ms delay
  1216. }
  1217.  
  1218. // Legacy function name for backward compatibility
  1219. function addTryFurnitureButton() {
  1220.     addTryFurnitureButtonDebounced();
  1221. }
  1222.  
  1223. function addTryFurnitureButtonInternal() {
  1224.     // Performance optimization - avoid excessive logging unless in debug mode
  1225.     if (window.COLORFLEX_DEBUG) {
  1226.         console.log("๐Ÿช‘ Adding Try Fabric button");
  1227.     }
  1228.    
  1229.     // Remove existing button if present
  1230.     const existingButton = document.getElementById('tryFurnitureBtn');
  1231.     if (existingButton) {
  1232.         existingButton.remove();
  1233.     }
  1234.    
  1235.     // Check compatibility
  1236.     const currentCollection = appState.selectedCollection?.name;
  1237.     if (!currentCollection) {
  1238.         if (window.COLORFLEX_DEBUG) {
  1239.             console.log("No current collection, skipping furniture button");
  1240.         }
  1241.         return;
  1242.     }
  1243.    
  1244.     const compatibleFurniture = getCompatibleFurniture(currentCollection);
  1245.     if (compatibleFurniture.length === 0) {
  1246.         if (window.COLORFLEX_DEBUG) {
  1247.             console.log("No compatible furniture found for", currentCollection);
  1248.         }
  1249.         return;
  1250.     }
  1251.    
  1252.     // Create button
  1253.     const button = document.createElement('button');
  1254.     button.id = 'tryFurnitureBtn';
  1255.     button.className = 'try-furniture-btn';
  1256.     button.innerHTML = `
  1257.         <span class="furniture-icon">๐Ÿช‘</span>
  1258.         <span class="button-text">Try Fabric (${compatibleFurniture.length})</span>
  1259.     `;
  1260.    
  1261.     // Add styles
  1262.     button.style.cssText = `
  1263.         position: absolute;
  1264.         bottom: 10px;
  1265.         right: 10px;
  1266.         background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  1267.         color: white;
  1268.         border: none;
  1269.         padding: 12px 18px;
  1270.         border-radius: 25px;
  1271.         font-family: 'Special Elite', monospace;
  1272.         font-size: 14px;
  1273.         font-weight: bold;
  1274.         cursor: pointer;
  1275.         box-shadow: 0 4px 15px rgba(0,0,0,0.2);
  1276.         transition: all 0.3s ease;
  1277.         display: flex;
  1278.         align-items: center;
  1279.         gap: 8px;
  1280.         z-index: 100;
  1281.     `;
  1282.    
  1283.     // Add hover effects
  1284.     button.addEventListener('mouseenter', () => {
  1285.         button.style.transform = 'translateY(-2px)';
  1286.         button.style.boxShadow = '0 6px 20px rgba(0,0,0,0.3)';
  1287.     });
  1288.    
  1289.     button.addEventListener('mouseleave', () => {
  1290.         button.style.transform = 'translateY(0)';
  1291.         button.style.boxShadow = '0 4px 15px rgba(0,0,0,0.2)';
  1292.     });
  1293.    
  1294.     // Add click handler
  1295.     button.addEventListener('click', () => {
  1296.         showFurnitureModal(compatibleFurniture);
  1297.     });
  1298.    
  1299.     // Find the room mockup container and add button
  1300.     const roomMockup = document.getElementById('roomMockup');
  1301.     if (roomMockup) {
  1302.         // Make sure the container is positioned relatively
  1303.         if (getComputedStyle(roomMockup).position === 'static') {
  1304.             roomMockup.style.position = 'relative';
  1305.         }
  1306.         roomMockup.appendChild(button);
  1307.         console.log("โœ… Try Furniture button added to room mockup");
  1308.     } else {
  1309.         console.error("โŒ Could not find room mockup container");
  1310.     }
  1311. }
  1312.  
  1313. // 3. showFurnitureModal function (also referenced but missing)
  1314. function showFurnitureModal(compatibleFurniture) {
  1315.     console.log("๐Ÿช‘ Showing furniture modal with", compatibleFurniture.length, "options");
  1316.    
  1317.     // Remove existing modal
  1318.     const existingModal = document.getElementById('furnitureModal');
  1319.     if (existingModal) {
  1320.         existingModal.remove();
  1321.     }
  1322.    
  1323.     // Create modal overlay
  1324.     const modalOverlay = document.createElement('div');
  1325.     modalOverlay.id = 'furnitureModal';
  1326.     modalOverlay.style.cssText = `
  1327.         position: fixed;
  1328.         top: 0;
  1329.         left: 0;
  1330.         width: 100%;
  1331.         height: 100%;
  1332.         background: rgba(0, 0, 0, 0.7);
  1333.         display: flex;
  1334.         justify-content: center;
  1335.         align-items: center;
  1336.         z-index: 1000;
  1337.         animation: fadeIn 0.3s ease;
  1338.     `;
  1339.    
  1340.     // Create modal content
  1341.     const modalContent = document.createElement('div');
  1342.     modalContent.style.cssText = `
  1343.         background: white;
  1344.         border-radius: 15px;
  1345.         padding: 30px;
  1346.         max-width: 600px;
  1347.         width: 90%;
  1348.         max-height: 80%;
  1349.         overflow-y: auto;
  1350.         box-shadow: 0 20px 60px rgba(0,0,0,0.3);
  1351.         animation: slideUp 0.3s ease;
  1352.     `;
  1353.    
  1354.     // Modal header
  1355.     const header = document.createElement('div');
  1356.     header.innerHTML = `
  1357.         <h2 style="margin: 0 0 20px 0; font-family: 'Special Elite', monospace; color: #333; text-align: center;">
  1358.             Choose Furniture for ${toInitialCaps(appState.selectedCollection.name)}
  1359.         </h2>
  1360.     `;
  1361.    
  1362.     // Furniture grid
  1363.     const furnitureGrid = document.createElement('div');
  1364.     furnitureGrid.style.cssText = `
  1365.         display: grid;
  1366.         grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  1367.         gap: 20px;
  1368.         margin-bottom: 20px;
  1369.     `;
  1370.    
  1371.     // Add furniture options
  1372.     compatibleFurniture.forEach(furniture => {
  1373.         const furnitureCard = document.createElement('div');
  1374.         furnitureCard.style.cssText = `
  1375.             border: 2px solid #e0e0e0;
  1376.             border-radius: 10px;
  1377.             padding: 15px;
  1378.             text-align: center;
  1379.             cursor: pointer;
  1380.             transition: all 0.3s ease;
  1381.             background: white;
  1382.         `;
  1383.        
  1384.         furnitureCard.innerHTML = `
  1385.             <img src="${normalizePath(furniture.thumbnail)}" alt="${furniture.name}"
  1386.                  style="width: 100%; height: 120px; object-fit: cover; border-radius: 8px; margin-bottom: 10px;"
  1387.                  onerror="this.style.background='#f0f0f0'; this.style.display='flex'; this.style.alignItems='center'; this.style.justifyContent='center'; this.innerHTML='๐Ÿช‘';">
  1388.             <h3 style="margin: 10px 0 5px 0; font-family: 'Special Elite', monospace; font-size: 16px;">${furniture.name}</h3>
  1389.             <p style="margin: 0; font-size: 12px; color: #666; line-height: 1.4;">${furniture.description}</p>
  1390.         `;
  1391.        
  1392.         // Hover effects
  1393.         furnitureCard.addEventListener('mouseenter', () => {
  1394.             furnitureCard.style.borderColor = '#667eea';
  1395.             furnitureCard.style.transform = 'translateY(-2px)';
  1396.             furnitureCard.style.boxShadow = '0 8px 25px rgba(0,0,0,0.1)';
  1397.         });
  1398.        
  1399.         furnitureCard.addEventListener('mouseleave', () => {
  1400.             furnitureCard.style.borderColor = '#e0e0e0';
  1401.             furnitureCard.style.transform = 'translateY(0)';
  1402.             furnitureCard.style.boxShadow = 'none';
  1403.         });
  1404.        
  1405.         // Click handler
  1406.         furnitureCard.addEventListener('click', () => {
  1407.             selectFurniture(furniture);
  1408.             modalOverlay.remove();
  1409.         });
  1410.        
  1411.         furnitureGrid.appendChild(furnitureCard);
  1412.     });
  1413.    
  1414.     // Cancel button
  1415.     const cancelButton = document.createElement('button');
  1416.     cancelButton.textContent = 'Cancel';
  1417.     cancelButton.style.cssText = `
  1418.         background: #ccc;
  1419.         color: #333;
  1420.         border: none;
  1421.         padding: 10px 20px;
  1422.         border-radius: 5px;
  1423.         cursor: pointer;
  1424.         font-family: 'Special Elite', monospace;
  1425.         display: block;
  1426.         margin: 0 auto;
  1427.     `;
  1428.    
  1429.     cancelButton.addEventListener('click', () => {
  1430.         modalOverlay.remove();
  1431.     });
  1432.    
  1433.     // Assemble modal
  1434.     modalContent.appendChild(header);
  1435.     modalContent.appendChild(furnitureGrid);
  1436.     modalContent.appendChild(cancelButton);
  1437.     modalOverlay.appendChild(modalContent);
  1438.    
  1439.     // Close on overlay click
  1440.     modalOverlay.addEventListener('click', (e) => {
  1441.         if (e.target === modalOverlay) {
  1442.             modalOverlay.remove();
  1443.         }
  1444.     });
  1445.    
  1446.     // Add CSS animations
  1447.     const style = document.createElement('style');
  1448.     style.textContent = `
  1449.         @keyframes fadeIn {
  1450.             from { opacity: 0; }
  1451.             to { opacity: 1; }
  1452.         }
  1453.         @keyframes slideUp {
  1454.             from { transform: translateY(50px); opacity: 0; }
  1455.             to { transform: translateY(0); opacity: 1; }
  1456.         }
  1457.     `;
  1458.     document.head.appendChild(style);
  1459.    
  1460.     document.body.appendChild(modalOverlay);
  1461. }
  1462.  
  1463. // 4. selectFurniture function
  1464. function selectFurniture(selectedFurniture) {
  1465.     console.log("๐Ÿช‘ Selected furniture:", selectedFurniture.name);
  1466.     console.log("๐Ÿงต Full furniture object:", selectedFurniture);
  1467.    
  1468.     // Store selected furniture in appState
  1469.     appState.selectedFurniture = selectedFurniture;
  1470.     appState.isInFabricMode = selectedFurniture.name === "Fabric";
  1471.    
  1472.     // Direct check for fabric name
  1473.     if (selectedFurniture.name === "Fabric") {
  1474.         console.log("๐Ÿงต ================================");
  1475.         console.log("๐Ÿงต FABRIC NAME DETECTED - CALLING FABRIC MOCKUP");
  1476.         console.log("๐Ÿงต ================================");
  1477.         renderFabricMockup();
  1478.         return;
  1479.     }
  1480.    
  1481.     // Switch to furniture mode for actual furniture
  1482.     console.log("๐Ÿช‘ Regular furniture selected, switching to furniture mode");
  1483.     switchToFurnitureMode(selectedFurniture);
  1484. }
  1485.  
  1486. // 5. addBackToPatternsButton function
  1487. function addBackToPatternsButton() {
  1488.     console.log("๐Ÿ”™ addBackToPatternsButton() called");
  1489.    
  1490.     const existingButton = document.getElementById('backToPatternsBtn');
  1491.     if (existingButton) {
  1492.         console.log("๐Ÿ—‘๏ธ Removing existing back button");
  1493.         existingButton.remove();
  1494.     }
  1495.    
  1496.     const button = document.createElement('button');
  1497.     button.id = 'backToPatternsBtn';
  1498.     button.innerHTML = `
  1499.         <span>โ† Back to Patterns</span>
  1500.     `;
  1501.    
  1502.     button.style.cssText = `
  1503.         position: absolute;
  1504.         bottom: 10px;
  1505.         left: 10px;
  1506.         background: linear-gradient(135deg, #ff7b7b 0%, #667eea 100%);
  1507.         color: white;
  1508.         border: none;
  1509.         padding: 12px 18px;
  1510.         border-radius: 25px;
  1511.         font-family: 'Special Elite', monospace;
  1512.         font-size: 14px;
  1513.         font-weight: bold;
  1514.         cursor: pointer;
  1515.         box-shadow: 0 4px 15px rgba(0,0,0,0.2);
  1516.         transition: all 0.3s ease;
  1517.         z-index: 100;
  1518.     `;
  1519.    
  1520.     console.log("๐Ÿ”— Adding click event listener to back button");
  1521.     button.addEventListener('click', (event) => {
  1522.         console.log("๐Ÿ”™ Back button clicked!");
  1523.         event.stopPropagation(); // Prevent zoom handler from receiving this event
  1524.         event.preventDefault();  // Prevent any default behavior
  1525.        
  1526.         // Check if we're in fabric mode or furniture mode
  1527.         if (appState.isInFabricMode) {
  1528.             console.log("๐Ÿงต Returning from fabric mode to patterns");
  1529.             returnToPatternsModeFromFabric();
  1530.         } else {
  1531.             console.log("๐Ÿช‘ Returning from furniture mode to patterns");
  1532.             returnToPatternsMode();
  1533.         }
  1534.     });
  1535.    
  1536.     const roomMockup = document.getElementById('roomMockup');
  1537.     if (roomMockup) {
  1538.         roomMockup.appendChild(button);
  1539.         console.log("โœ… Back button added to DOM");
  1540.        
  1541.         // Test if button is actually clickable
  1542.         console.log("๐Ÿงช Button in DOM:", document.getElementById('backToPatternsBtn'));
  1543.         console.log("๐Ÿงช Button parent:", document.getElementById('backToPatternsBtn')?.parentElement);
  1544.     } else {
  1545.         console.error("โŒ roomMockup not found!");
  1546.     }
  1547. }
  1548.  
  1549. // Function to return from fabric mode to patterns mode
  1550. function returnToPatternsModeFromFabric() {
  1551.     console.log("๐Ÿงต Returning from fabric mode to patterns");
  1552.    
  1553.     // Clear fabric mode state
  1554.     appState.selectedFurniture = null;
  1555.     appState.isInFabricMode = false;
  1556.    
  1557.     // Remove back button
  1558.     const backButton = document.getElementById('backToPatternsBtn');
  1559.     if (backButton) {
  1560.         backButton.remove();
  1561.     }
  1562.    
  1563.     // Remove fabric tuning controls
  1564.     removeFabricTuningControls();
  1565.    
  1566.     // Re-add try furniture button
  1567.     addTryFurnitureButton();
  1568.    
  1569.     // Trigger room mockup update to show regular pattern view
  1570.     if (appState.currentPattern) {
  1571.         updateRoomMockup();
  1572.     }
  1573.    
  1574.     console.log("โœ… Returned from fabric mode to patterns mode");
  1575. }
  1576.  
  1577. // 6. initializeTryFurnitureFeature function
  1578. function initializeTryFurnitureFeature() {
  1579.     console.log("๐Ÿช‘ Initializing Try Furniture feature");
  1580.    
  1581.     // Add the button when a collection is loaded
  1582.     if (appState.selectedCollection && !appState.furnitureMode) {
  1583.         addTryFurnitureButton();
  1584.     }
  1585. }
  1586.  
  1587. // Resolve furniture pattern paths using collection-based structure
  1588. function resolveFurniturePatternPaths(furnitureConfig, collectionName, patternName, originalPatternLayers) {
  1589.     console.log(`๐Ÿ” Resolving furniture pattern paths:`);
  1590.     console.log(`   Collection: "${collectionName}"`);
  1591.     console.log(`   Pattern: "${patternName}"`);
  1592.    
  1593.     // โœ… VALIDATION: Make sure we have a valid collection name
  1594.     if (!collectionName || collectionName === "UNKNOWN" || collectionName === patternName) {
  1595.         console.error(`โŒ Invalid collection name: "${collectionName}"`);
  1596.         console.error(`   Pattern name: "${patternName}"`);
  1597.         console.error(`   These should be different!`);
  1598.        
  1599.         // Try to get it from the current furniture collection
  1600.         const fallbackCollectionName = appState.selectedCollection?.originalCollectionName;
  1601.         if (fallbackCollectionName) {
  1602.             console.log(`๐Ÿ”ง Using fallback collection name: "${fallbackCollectionName}"`);
  1603.             collectionName = fallbackCollectionName;
  1604.         } else {
  1605.             console.error(`โŒ No fallback collection name available!`);
  1606.             return [];
  1607.         }
  1608.     }
  1609.    
  1610.     const patternSlug = createPatternSlug(patternName);
  1611.    
  1612.     // Replace template variables
  1613.     const patternFolder = furnitureConfig.patternPathTemplate
  1614.         .replace('{collection}', collectionName)
  1615.         .replace('{patternSlug}', patternSlug);
  1616.    
  1617.     console.log(`   Pattern slug: "${patternSlug}"`);
  1618.     console.log(`   โœ… Final folder: "${patternFolder}"`);
  1619.    
  1620.     // Map layers to furniture paths
  1621.     const furniturePatternLayers = originalPatternLayers.map((layer, index) => {
  1622.         const originalFileName = layer.path.split('/').pop();
  1623.         const layerName = originalFileName.replace(/\.[^/.]+$/, '');
  1624.         const cleanLayerName = layerName.replace(/^[^_]*_/, ''); // Remove everything before first underscore
  1625.         const furnitureFileName = `${patternSlug}_${cleanLayerName}.png`;
  1626.         const furniturePath = `${patternFolder}${furnitureFileName}`;
  1627.        
  1628.         return {
  1629.             ...layer,
  1630.             path: furniturePath,
  1631.             originalPath: layer.path,
  1632.             furnitureFileName: furnitureFileName
  1633.         };
  1634.     });
  1635.    
  1636.     return furniturePatternLayers;
  1637. }
  1638.  
  1639.  
  1640. function createFurniturePattern(originalPattern, furnitureConfig, collectionName) {
  1641.     console.log(`๐Ÿ”„ Creating furniture pattern:`);
  1642.     console.log(`   Pattern: ${originalPattern.name}`);
  1643.     console.log(`   Collection: ${collectionName}`);
  1644.     console.log(`   Furniture: ${furnitureConfig.name}`);
  1645.    
  1646.     // โœ… VERIFY: Make sure collectionName is correct
  1647.     if (!collectionName || collectionName === originalPattern.name) {
  1648.         console.error(`โŒ COLLECTION NAME ERROR!`);
  1649.         console.error(`   Expected collection name like "botanicals"`);
  1650.         console.error(`   Got: "${collectionName}"`);
  1651.         console.error(`   Pattern name: "${originalPattern.name}"`);
  1652.         console.error(`   These should be different!`);
  1653.     }
  1654.    
  1655.     const furniturePatternLayers = resolveFurniturePatternPaths(
  1656.         furnitureConfig,
  1657.         collectionName,           // โ† This should be "botanicals"
  1658.         originalPattern.name,     // โ† This should be "Key Largo"
  1659.         originalPattern.layers || []
  1660.     );
  1661.    
  1662.     const furniturePattern = {
  1663.         ...originalPattern,
  1664.         layers: furniturePatternLayers,
  1665.         isFurniture: true,
  1666.         furnitureConfig: furnitureConfig,
  1667.         originalPattern: originalPattern,
  1668.         collectionName: collectionName // Store collection name for reference
  1669.     };
  1670.    
  1671.     console.log(`โœ… Created furniture pattern with ${furniturePatternLayers.length} layers`);
  1672.     console.log(`   Expected path pattern: data/furniture/.../patterns/${collectionName}/${createPatternSlug(originalPattern.name)}/`);
  1673.    
  1674.     return furniturePattern;
  1675. }
  1676.  
  1677.  
  1678. // Updated switchToFurnitureMode function
  1679. function switchToFurnitureMode(furniture) {
  1680.     console.log("๐Ÿ”„ Switching to furniture mode for:", furniture.name);
  1681.    
  1682.     // โœ… SIMPLE: Just grab the current collection name RIGHT NOW
  1683.     const originalCollectionName = appState.selectedCollection.name;
  1684.     console.log(`๐Ÿ“ Original collection name: "${originalCollectionName}"`);
  1685.    
  1686.     // Store the ENTIRE original collection
  1687.     appState.originalCollection = { ...appState.selectedCollection };
  1688.    
  1689.     // Convert all patterns to furniture patterns using the CURRENT collection name
  1690.     const furniturePatterns = appState.selectedCollection.patterns.map(pattern => {
  1691.         return createFurniturePattern(pattern, furniture.config, originalCollectionName);
  1692.     });
  1693.    
  1694.     // Create virtual furniture collection
  1695.     const furnitureCollection = {
  1696.         name: `${originalCollectionName.toUpperCase()} ${furniture.name.toUpperCase()}`,
  1697.         patterns: furniturePatterns,
  1698.         curatedColors: appState.selectedCollection.curatedColors,
  1699.         coordinates: [],
  1700.         mockup: null,
  1701.         furnitureType: furniture.id,
  1702. wallMask: furniture.config.wallMask || "default-wall-mask.png",  // โ† Ensure it's not null
  1703.         // โœ… SIMPLE: Store the original collection name directly
  1704.         originalCollectionName: originalCollectionName,
  1705.         furnitureConfig: furniture.config
  1706.     };
  1707.    
  1708.     // Update app state
  1709.     appState.selectedCollection = furnitureCollection;
  1710.     appState.furnitureMode = true;
  1711.    
  1712.     console.log(`โœ… Switched to furniture mode. Paths will use: "${originalCollectionName}"`);
  1713.    
  1714.     // Update UI
  1715.     if (dom.collectionHeader) {
  1716.         dom.collectionHeader.textContent = furnitureCollection.name;
  1717.     }
  1718.    
  1719.     // Remove try furniture button and add back button
  1720.     const tryButton = document.getElementById('tryFurnitureBtn');
  1721.     if (tryButton) tryButton.remove();
  1722.     addBackToPatternsButton();
  1723.    
  1724.     // Trigger re-render
  1725.     if (appState.currentPattern) {
  1726.         const furniturePattern = furniturePatterns.find(p => p.id === appState.currentPattern.id);
  1727.         if (furniturePattern) {
  1728.             loadPatternData(appState.selectedCollection, furniturePattern.id);
  1729.         }
  1730.     }
  1731. }
  1732.  
  1733. function returnToPatternsMode() {
  1734.     console.log("๐Ÿ”„ Returning to patterns mode");
  1735.    
  1736.     // Restore original collection
  1737.     if (appState.originalCollection) {
  1738.         console.log("๐Ÿ”„ Restoring original collection:", appState.originalCollection.name);
  1739.        
  1740.         appState.selectedCollection = appState.originalCollection; // Remove .fullCollection
  1741.         appState.furnitureMode = false;
  1742.         appState.originalCollection = null;
  1743.        
  1744.         // Clear fabric mode state
  1745.         appState.selectedFurniture = null;
  1746.         appState.isInFabricMode = false;
  1747.        
  1748.         // Update UI
  1749.         if (dom.collectionHeader) {
  1750.             dom.collectionHeader.textContent = toInitialCaps(appState.selectedCollection.name);
  1751.         }
  1752.        
  1753.         // Remove back button
  1754.         const backButton = document.getElementById('backToPatternsBtn');
  1755.         if (backButton) {
  1756.             backButton.remove();
  1757.         }
  1758.        
  1759.         // Re-add try furniture button
  1760.         addTryFurnitureButton();
  1761.        
  1762.         // Trigger re-render in patterns mode
  1763.         if (appState.currentPattern) {
  1764.             // Find the original pattern (not the furniture version)
  1765.             const originalPattern = appState.selectedCollection.patterns.find(p => p.id === appState.currentPattern.id);
  1766.             if (originalPattern) {
  1767.                 loadPatternData(appState.selectedCollection, originalPattern.id);
  1768.             }
  1769.         }
  1770.        
  1771.         console.log("โœ… Returned to patterns mode");
  1772.     } else {
  1773.         console.error("โŒ Cannot return to patterns mode - original collection not found");
  1774.     }
  1775. }
  1776.  
  1777.  
  1778. // Development helper: Generate expected folder structure
  1779. function generateFolderStructure(collectionName, furnitureId) {
  1780.     const collection = appState.collections?.find(c => c.name === collectionName);
  1781.     const furniture = furnitureConfig?.[furnitureId];
  1782.    
  1783.     if (!collection || !furniture) {
  1784.         console.error("โŒ Collection or furniture not found");
  1785.         return;
  1786.     }
  1787.    
  1788.     console.log(`๐Ÿ“ FOLDER STRUCTURE for ${furniture.name} + ${collectionName}:`);
  1789.     console.log(`๐Ÿ“ Base path: data/furniture/${furnitureId}/patterns/${collectionName}/`);
  1790.     console.log(`๐Ÿ“ Folders needed:`);
  1791.    
  1792.     const folders = [];
  1793.     collection.patterns.forEach(pattern => {
  1794.         const slug = createPatternSlug(pattern.name);
  1795.         const folder = `data/furniture/${furnitureId}/patterns/${collectionName}/${slug}/`;
  1796.         folders.push({
  1797.             pattern: pattern.name,
  1798.             slug: slug,
  1799.             folder: folder
  1800.         });
  1801.         console.log(`   ${folder}`);
  1802.     });
  1803.    
  1804.     console.log(`๐Ÿ“Š Total folders needed: ${folders.length}`);
  1805.     return folders;
  1806. }
  1807.  
  1808. // Development helper: Check what files are expected for a pattern
  1809. function getExpectedFiles(collectionName, patternName, furnitureId) {
  1810.     const collection = appState.collections?.find(c => c.name === collectionName);
  1811.     const pattern = collection?.patterns.find(p => p.name === patternName);
  1812.     const furniture = furnitureConfig?.[furnitureId];
  1813.    
  1814.     if (!pattern || !furniture) {
  1815.         console.error("โŒ Pattern or furniture not found");
  1816.         return;
  1817.     }
  1818.    
  1819.     const slug = createPatternSlug(patternName);
  1820.     const folder = `https://so-animation.com/colorflex/data/furniture/${furnitureId}/patterns/${collectionName}/${slug}/`;
  1821.    
  1822.     console.log(`๐Ÿ“‹ EXPECTED FILES for ${patternName} on ${furniture.name}:`);
  1823.     console.log(`๐Ÿ“ Folder: ${folder}`);
  1824.     console.log(`๐Ÿ“„ Files needed:`);
  1825.    
  1826.     const expectedFiles = [];
  1827.     if (pattern.layers) {
  1828.         pattern.layers.forEach((layer, index) => {
  1829.             const originalFileName = layer.path.split('/').pop();
  1830.             const layerName = originalFileName.replace(/\.[^/.]+$/, '');
  1831.             const furnitureFileName = `${slug}-${layerName}.png`;
  1832.             expectedFiles.push({
  1833.                 original: originalFileName,
  1834.                 furniture: furnitureFileName,
  1835.                 fullPath: `${folder}${furnitureFileName}`
  1836.             });
  1837.             console.log(`   ${furnitureFileName}`);
  1838.         });
  1839.     }
  1840.    
  1841.     return {
  1842.         folder: folder,
  1843.         files: expectedFiles
  1844.     };
  1845. }
  1846. // 1. Console commands for planning your work
  1847. window.workflowHelpers = {
  1848.    
  1849.     // See all expected folders for a furniture + collection combo
  1850.     showFolders: function(furnitureId, collectionName) {
  1851.         console.log(`๐Ÿ“ FOLDER STRUCTURE: ${furnitureId} + ${collectionName}`);
  1852.         return generateFolderStructure(collectionName, furnitureId);
  1853.     },
  1854.    
  1855.     // See expected files for a specific pattern
  1856.     showFiles: function(collectionName, patternName, furnitureId) {
  1857.         console.log(`๐Ÿ“„ EXPECTED FILES: ${patternName} on ${furnitureId}`);
  1858.         return getExpectedFiles(collectionName, patternName, furnitureId);
  1859.     },
  1860.    
  1861.     // Get overview of all work needed
  1862.     showPlan: function() {
  1863.         console.log(`๐ŸŽจ COMPLETE RENDERING PLAN`);
  1864.         return generateRenderingPlan();
  1865.     },
  1866.    
  1867.     // Test pattern slug generation
  1868.     testSlug: function(patternName) {
  1869.         const slug = createPatternSlug(patternName);
  1870.         console.log(`Pattern: "${patternName}" โ†’ Slug: "${slug}"`);
  1871.         return slug;
  1872.     },
  1873.    
  1874.     // Check what's compatible
  1875.     showCompatibility: function() {
  1876.         console.log(`๐Ÿ”— FURNITURE COMPATIBILITY:`);
  1877.         Object.entries(furnitureConfig || {}).forEach(([furnitureId, furniture]) => {
  1878.             console.log(`${furniture.name}: ${furniture.compatibleCollections.join(', ')}`);
  1879.         });
  1880.     },
  1881.    
  1882.     // Generate folder creation script
  1883.     generateFolderScript: function(furnitureId) {
  1884.         const furniture = furnitureConfig?.[furnitureId];
  1885.         if (!furniture) {
  1886.             console.error(`โŒ Furniture ${furnitureId} not found`);
  1887.             return;
  1888.         }
  1889.        
  1890.         console.log(`๐Ÿ“œ FOLDER CREATION SCRIPT for ${furniture.name}:`);
  1891.         console.log(`# Copy and paste these commands to create folders:\n`);
  1892.        
  1893.         let script = `# Furniture: ${furniture.name}\n`;
  1894.         script += `mkdir -p data/furniture/${furnitureId}/patterns\n\n`;
  1895.        
  1896.         furniture.compatibleCollections.forEach(collectionName => {
  1897.             const collection = appState.collections?.find(c => c.name === collectionName);
  1898.             if (!collection) return;
  1899.            
  1900.             script += `# Collection: ${collectionName}\n`;
  1901.             script += `mkdir -p data/furniture/${furnitureId}/patterns/${collectionName}\n`;
  1902.            
  1903.             collection.patterns.forEach(pattern => {
  1904.                 const slug = createPatternSlug(pattern.name);
  1905.                 script += `mkdir -p data/furniture/${furnitureId}/patterns/${collectionName}/${slug}\n`;
  1906.             });
  1907.             script += `\n`;
  1908.         });
  1909.        
  1910.         console.log(script);
  1911.         return script;
  1912.     }
  1913. };
  1914.  
  1915. // 2. Development status checker
  1916. function checkFurnitureImplementationStatus() {
  1917.     console.log(`๐Ÿ” FURNITURE IMPLEMENTATION STATUS CHECK:`);
  1918.     console.log(`======================================`);
  1919.    
  1920.     // Check if furniture config is loaded
  1921.     if (!furnitureConfig) {
  1922.         console.log(`โŒ furnitureConfig not loaded`);
  1923.         return;
  1924.     }
  1925.     console.log(`โœ… furnitureConfig loaded: ${Object.keys(furnitureConfig).length} furniture pieces`);
  1926.    
  1927.     // Check collections
  1928.     if (!appState.collections || appState.collections.length === 0) {
  1929.         console.log(`โŒ Collections not loaded`);
  1930.         return;
  1931.     }
  1932.     console.log(`โœ… Collections loaded: ${appState.collections.length} collections`);
  1933.    
  1934.     // Check current state
  1935.     const currentCollection = appState.selectedCollection?.name;
  1936.     if (!currentCollection) {
  1937.         console.log(`โŒ No collection currently selected`);
  1938.         return;
  1939.     }
  1940.     console.log(`โœ… Current collection: ${currentCollection}`);
  1941.    
  1942.     // Check compatibility
  1943.     const compatible = getCompatibleFurniture(currentCollection);
  1944.     console.log(`โœ… Compatible furniture: ${compatible.length} pieces`);
  1945.     compatible.forEach(f => console.log(`   - ${f.name}`));
  1946.    
  1947.     // Check if Try Furniture button should be visible
  1948.     const tryButton = document.getElementById('tryFurnitureBtn');
  1949.     const backButton = document.getElementById('backToPatternsBtn');
  1950.    
  1951.     if (appState.furnitureMode) {
  1952.         console.log(`๐Ÿช‘ Currently in FURNITURE MODE`);
  1953.         console.log(`   Back button present: ${!!backButton}`);
  1954.     } else {
  1955.         console.log(`๐ŸŽจ Currently in PATTERN MODE`);
  1956.         console.log(`   Try Furniture button present: ${!!tryButton}`);
  1957.         if (!tryButton && compatible.length > 0) {
  1958.             console.log(`โš ๏ธ  Try Furniture button should be visible but isn't!`);
  1959.        }
  1960.    }
  1961.    
  1962.    return {
  1963.        furnitureConfigLoaded: !!furnitureConfig,
  1964.        collectionsLoaded: appState.collections?.length > 0,
  1965.        currentCollection: currentCollection,
  1966.        compatibleFurniture: compatible.length,
  1967.        furnitureMode: appState.furnitureMode,
  1968.        tryButtonPresent: !!tryButton,
  1969.        backButtonPresent: !!backButton
  1970.    };
  1971. }
  1972.  
  1973. // 3. Easy console commands
  1974. window.checkStatus = checkFurnitureImplementationStatus;
  1975.  
  1976. // 4. Example usage guide
  1977. // Workflow helpers available in development mode only
  1978. if (window.location.hostname === 'localhost' || window.location.hostname.includes('dev')) {
  1979.    console.log(`
  1980. ๐Ÿช‘ FURNITURE WORKFLOW HELPERS LOADED!
  1981. =====================================
  1982.  
  1983. Console Commands:
  1984. โ€ข workflowHelpers.showPlan() - See complete rendering plan
  1985. โ€ข workflowHelpers.showFolders('sofa-capitol', 'botanicals') - See folder structure
  1986. โ€ข workflowHelpers.showFiles('botanicals', 'Key Largo', 'sofa-capitol') - See expected files
  1987. โ€ข workflowHelpers.testSlug('Pattern Name Here') - Test slug conversion
  1988. โ€ข workflowHelpers.showCompatibility() - See what's compatible with what
  1989. โ€ข workflowHelpers.generateFolderScript('sofa-capitol') - Generate mkdir commands
  1990. โ€ข checkStatus() - Check implementation status
  1991.  
  1992. Example Workflow:
  1993. 1. workflowHelpers.showPlan() - See total work needed
  1994. 2. workflowHelpers.generateFolderScript('sofa-capitol') - Create folders
  1995. 3. Render patterns and save to generated folders
  1996. 4. Test with Try Furniture button!
  1997. `);
  1998. }
  1999.  
  2000. // 5. Integration check
  2001. document.addEventListener('DOMContentLoaded', () => {
  2002.     // Wait a bit for everything to load
  2003.     setTimeout(() => {
  2004.         console.log(`๐Ÿ” Running furniture integration check...`);
  2005.         checkFurnitureImplementationStatus();
  2006.     }, 2000);
  2007. });
  2008.  
  2009. // Load furniture config on app init
  2010. let furnitureConfig = null;
  2011.  
  2012. async function loadFurnitureConfig() {
  2013.     try {
  2014.         console.log("๐Ÿ“ Loading furniture config...");
  2015.         let response;
  2016.         const furnitureConfigUrl = window.ColorFlexAssets?.furnitureConfigUrl || '/assets/furniture-config.json';
  2017.         response = await fetch(furnitureConfigUrl, {
  2018.             method: 'GET',
  2019.             cache: 'no-cache',
  2020.             headers: {
  2021.                 'Content-Type': 'application/json',
  2022.             }
  2023.         });
  2024.        
  2025.         if (response.ok) {
  2026.             furnitureConfig = await response.json();
  2027.             console.log('โœ… Loaded furniture config:', furnitureConfig);
  2028.            
  2029.             // Debug the structure
  2030.             Object.keys(furnitureConfig).forEach(key => {
  2031.                 console.log(`  ${key}:`, Object.keys(furnitureConfig[key]));
  2032.             });
  2033.         } else {
  2034.             if (response.status === 0 || response.status === 403) {
  2035.                 throw new Error('CORS Error: Cross-origin request blocked');
  2036.             }
  2037.             console.error("โŒ Furniture config response not ok:", response.status);
  2038.         }
  2039.     } catch (e) {
  2040.         if (e.name === 'TypeError' && e.message.includes('fetch')) {
  2041.             console.error('โŒ Network/CORS Error loading furniture config:', e);
  2042.         } else {
  2043.             console.error("โŒ Error loading furniture config:", e);
  2044.         }
  2045.     }
  2046. }
  2047.  
  2048.  
  2049. dom._patternName = document.getElementById("patternName"); // Initial assignment
  2050.  
  2051. // Fetch colors from colors.json
  2052. async function loadColors() {
  2053.     try {
  2054.         // Check if colors are embedded (Shopify mode)
  2055.         if (window.ColorFlexData && window.ColorFlexData.colors) {
  2056.             console.log("๐ŸŽฏ Using embedded Sherwin-Williams colors");
  2057.             appState.colorsData = window.ColorFlexData.colors;
  2058.             console.log("โœ… Colors loaded:", appState.colorsData.length);
  2059.             return;
  2060.         }
  2061.        
  2062.         // Load directly from Shopify assets
  2063.         console.log("๐Ÿ“ Loading colors from Shopify assets");
  2064.         const colorsUrl = window.ColorFlexAssets?.colorsUrl || "/assets/colors.json";
  2065.         const response = await fetch(colorsUrl, {
  2066.             method: 'GET',
  2067.             cache: 'no-cache',
  2068.             headers: {
  2069.                 'Content-Type': 'application/json',
  2070.             }
  2071.         });
  2072.         if (!response.ok) {
  2073.             if (response.status === 0 || response.status === 403) {
  2074.                 throw new Error('CORS Error: Cross-origin request blocked');
  2075.             }
  2076.             throw new Error(`HTTP error: ${response.status}`);
  2077.         }
  2078.        
  2079.         const data = await response.json();
  2080.         if (!Array.isArray(data) || data.length === 0) {
  2081.             throw new Error("Colors data is empty or invalid");
  2082.         }
  2083.  
  2084.         appState.colorsData = data;
  2085.         console.log("โœ… Colors loaded:", appState.colorsData.length);
  2086.     } catch (err) {
  2087.         console.error("รขยล’ Error loading colors:", err);
  2088.         alert("Failed to load Sherwin-Williams colors.");
  2089.     }
  2090. }
  2091.  
  2092. // Lookup color from colors.json data
  2093. let lookupColor = (colorName) => {
  2094.     if (!colorName || typeof colorName !== "string") {
  2095.         console.warn(`Invalid colorName: ${colorName}, defaulting to #FFFFFF`);
  2096.         return "#FFFFFF";
  2097.     }
  2098.     const cleanedColorName = colorName.replace(/^(SW|SC)\d+\s*/i, "").toLowerCase().trim();
  2099.     console.log(`lookupColor: cleanedColorName=${cleanedColorName}`);
  2100.     if (/^#[0-9A-F]{6}$/i.test(cleanedColorName)) {
  2101.         console.log(`lookupColor: ${colorName} is a hex value, returning ${cleanedColorName}`);
  2102.         return cleanedColorName;
  2103.     }
  2104.     const colorEntry = appState.colorsData.find(c => c.color_name.toLowerCase() === cleanedColorName);
  2105.     if (!colorEntry) {
  2106.         console.warn(`Color '${cleanedColorName}' not found in colorsData, defaulting to #FFFFFF`);
  2107.         return "#FFFFFF";
  2108.     }
  2109.     console.log(`Looked up ${colorName} -> #${colorEntry.hex}`);
  2110.     return `#${colorEntry.hex}`;
  2111. };
  2112. if (USE_GUARD && DEBUG_TRACE) {
  2113.     lookupColor = guard(traceWrapper(lookupColor, "lookupColor")); // Wrapped for debugging
  2114. } else if (USE_GUARD) {
  2115.     lookupColor = guard(lookupColor, "lookupColor"); // Wrapped for debugging
  2116. }
  2117.  
  2118. // Hamburger menu functionality
  2119. document.addEventListener('DOMContentLoaded', function() {
  2120.     const hamburgerBtn = document.getElementById('hamburgerBtn');
  2121.     const sidebar = document.getElementById('leftSidebar');
  2122.    
  2123.     if (hamburgerBtn && sidebar) {
  2124.         hamburgerBtn.addEventListener('click', function() {
  2125.             hamburgerBtn.classList.toggle('active');
  2126.             sidebar.classList.toggle('open');
  2127.         });
  2128.        
  2129.         // Close sidebar when clicking outside on mobile
  2130.         document.addEventListener('click', function(e) {
  2131.             if (window.innerWidth <= 1023 &&
  2132.                 !sidebar.contains(e.target) &&
  2133.                 !hamburgerBtn.contains(e.target) &&
  2134.                 sidebar.classList.contains('open')) {
  2135.                 hamburgerBtn.classList.remove('active');
  2136.                 sidebar.classList.remove('open');
  2137.             }
  2138.         });
  2139.     }
  2140. });
  2141.  
  2142. // Check if a specific pattern has furniture renders
  2143. async function checkFurnitureAvailability(patternName) {
  2144.   const patternSlug = patternName.toLowerCase().replace(/ /g, '-');
  2145.   const manifestUrl = `data/furniture/sofa-capitol/patterns/${patternSlug}/manifest.json`;
  2146.  
  2147.   try {
  2148.     const response = await fetch(manifestUrl, {
  2149.       method: 'GET',
  2150.       mode: 'cors',
  2151.       cache: 'no-cache',
  2152.       headers: {
  2153.         'Content-Type': 'application/json',
  2154.       }
  2155.     });
  2156.     if (response.ok) {
  2157.       const manifest = await response.json();
  2158.       return {
  2159.         available: true,
  2160.         manifest: manifest,
  2161.         furnitureType: 'sofa-capitol'
  2162.       };
  2163.     }
  2164.   } catch (e) {
  2165.     // No furniture version
  2166.   }
  2167.   return { available: false };
  2168. }
  2169.  
  2170. // Call loadFurnitureConfig when your app initializes
  2171. loadFurnitureConfig();
  2172.  
  2173.  
  2174.  
  2175. // Utility Functions
  2176.  
  2177. // Helper function for scaling
  2178. function scaleToFit(img, targetWidth, targetHeight) {
  2179.     const aspectRatio = img.width / img.height;
  2180.     let drawWidth = targetWidth;
  2181.     let drawHeight = targetHeight;
  2182.    
  2183.     if (aspectRatio > targetWidth / targetHeight) {
  2184.         drawHeight = drawWidth / aspectRatio;
  2185.     } else {
  2186.         drawWidth = drawHeight * aspectRatio;
  2187.     }
  2188.    
  2189.     const x = (targetWidth - drawWidth) / 2;
  2190.     const y = (targetHeight - drawHeight) / 2;
  2191.    
  2192.     return { width: drawWidth, height: drawHeight, x, y };
  2193. }
  2194. // Shared helper for loading and tinting a masked image
  2195. async function drawMaskedLayer(imgPath, tintColor, label) {
  2196.     // Check if this is a wall panel image
  2197.     const isWallPanel = imgPath.includes('wall-panels');
  2198.    
  2199.     // Get the original, untinted grayscale image for alpha calculation
  2200.     const originalUrl = await new Promise(resolve =>
  2201.         processImage(imgPath, resolve, null, 2.2, false, false, false)
  2202.     );
  2203.     const img = await loadImage(originalUrl);
  2204.  
  2205.     // Draw the original image centered on an offscreen canvas
  2206.     const offscreen = document.createElement("canvas");
  2207.     offscreen.width = 1080;
  2208.     offscreen.height = 1080;
  2209.     const offCtx = offscreen.getContext("2d");
  2210.     drawCenteredImage(offCtx, img, 1080, 1080);
  2211.  
  2212.     // Get pixel data
  2213.     let imageData;
  2214.     try {
  2215.         imageData = offCtx.getImageData(0, 0, 1080, 1080);
  2216.     } catch (e) {
  2217.         console.warn("โš ๏ธ Canvas tainted, skipping masked layer processing:", e.message);
  2218.         return;
  2219.     }
  2220.     const { data } = imageData;
  2221.  
  2222.     // Invert luminance for alpha: white (255) รขโ€ โ€™ alpha 0, black (0) รขโ€ โ€™ alpha 255
  2223.     for (let i = 0; i < data.length; i += 4) {
  2224.         const r = data[i];
  2225.         const g = data[i + 1];
  2226.         const b = data[i + 2];
  2227.         const luminance = 0.299 * r + 0.587 * g + 0.114 * b;
  2228.         data[i + 3] = 255 - luminance; // INVERTED for correct alpha
  2229.     }
  2230.     offCtx.putImageData(imageData, 0, 0);
  2231.  
  2232.     // Prepare the colored (tint) layer and mask it with the alpha
  2233.     const tintLayer = document.createElement("canvas");
  2234.     tintLayer.width = 1080;
  2235.     tintLayer.height = 1080;
  2236.     const tintCtx = tintLayer.getContext("2d");
  2237.     tintCtx.fillStyle = tintColor;
  2238.     tintCtx.fillRect(0, 0, 1080, 1080);
  2239.     tintCtx.globalCompositeOperation = "destination-in";
  2240.     tintCtx.drawImage(offscreen, 0, 0);
  2241.  
  2242.     // Composite result onto main canvas
  2243.     ctx.globalAlpha = 1.0;
  2244.     ctx.globalCompositeOperation = "source-over";
  2245.     ctx.drawImage(tintLayer, 0, 0);
  2246.  
  2247.     console.log(`โœ… [${label}] tint-mask drawn.`);
  2248. }
  2249.  
  2250. function applyNormalizationProcessing(data, rLayer, gLayer, bLayer) {
  2251.     // IMPROVED normalization logic for better detail preservation
  2252.     let minLuminance = 255, maxLuminance = 0;
  2253.     for (let i = 0; i < data.length; i += 4) {
  2254.         const luminance = 0.299 * data[i] + 0.587 * data[i + 1] + 0.114 * data[i + 2];
  2255.         minLuminance = Math.min(minLuminance, luminance);
  2256.         maxLuminance = Math.max(maxLuminance, luminance);
  2257.     }
  2258.     const range = maxLuminance - minLuminance || 1;
  2259.     console.log("Min Luminance:", minLuminance, "Max Luminance:", maxLuminance);
  2260.  
  2261.     for (let i = 0; i < data.length; i += 4) {
  2262.         const luminance = 0.299 * data[i] + 0.587 * data[i + 1] + 0.114 * data[i + 2];
  2263.         let normalized = (luminance - minLuminance) / range;
  2264.         normalized = Math.max(0, Math.min(1, normalized));
  2265.        
  2266.         let alpha = 1 - normalized;
  2267.        
  2268.         if (alpha > 0.8) {
  2269.             alpha = 1;
  2270.         } else if (alpha > 0.5) {
  2271.             alpha = 0.8 + (alpha - 0.5) * 0.67;
  2272.         } else if (alpha > 0.2) {
  2273.             alpha = alpha * 1.6;
  2274.         } else {
  2275.             alpha = alpha * 0.5;
  2276.         }
  2277.         alpha = Math.min(1, Math.max(0, alpha));
  2278.  
  2279.         if (alpha > 0.05) {
  2280.             data[i] = rLayer;
  2281.             data[i + 1] = gLayer;
  2282.             data[i + 2] = bLayer;
  2283.         } else {
  2284.             data[i] = 0;
  2285.             data[i + 1] = 0;
  2286.             data[i + 2] = 0;
  2287.         }
  2288.         data[i + 3] = Math.round(alpha * 255);
  2289.     }
  2290. }
  2291.  
  2292. function resolveColor(raw) {
  2293.     const color = (!raw || typeof raw !== "string") ? "Snowbound" : raw.trim().toUpperCase();
  2294.     const resolved = lookupColor(color);
  2295.     if (!resolved) console.warn(`รขลก รฏยธย [resolveColor] Could not resolve color: '${color}', using Snowbound`);
  2296.     return resolved || lookupColor("Snowbound") || "#DDDDDD";
  2297. }
  2298.  
  2299. function drawCenteredImage(ctx, img, canvasWidth, canvasHeight) {
  2300.     const aspect = img.width / img.height;
  2301.     let drawWidth = canvasWidth;
  2302.     let drawHeight = drawWidth / aspect;
  2303.  
  2304.     if (drawHeight > canvasHeight) {
  2305.         drawHeight = canvasHeight;
  2306.         drawWidth = drawHeight * aspect;
  2307.     }
  2308.  
  2309.     const offsetX = Math.round((canvasWidth - drawWidth) / 2);
  2310.     const offsetY = Math.round((canvasHeight - drawHeight) / 2);
  2311.     ctx.drawImage(img, offsetX, offsetY, drawWidth, drawHeight);
  2312. }
  2313.  
  2314. function hexToHSL(hex) {
  2315.     // Remove # if present
  2316.     hex = hex.replace(/^#/, '');
  2317.  
  2318.     // Convert 3-digit to 6-digit hex
  2319.     if (hex.length === 3) {
  2320.         hex = hex.split('').map(x => x + x).join('');
  2321.     }
  2322.  
  2323.     if (hex.length !== 6) {
  2324.         console.error("รขยล’ Invalid HEX color:", hex);
  2325.         return null;
  2326.     }
  2327.  
  2328.     const r = parseInt(hex.substr(0, 2), 16) / 255;
  2329.     const g = parseInt(hex.substr(2, 2), 16) / 255;
  2330.     const b = parseInt(hex.substr(4, 2), 16) / 255;
  2331.  
  2332.     const max = Math.max(r, g, b);
  2333.     const min = Math.min(r, g, b);
  2334.     let h, s, l = (max + min) / 2;
  2335.  
  2336.     if (max === min) {
  2337.         h = s = 0; // achromatic
  2338.     } else {
  2339.         const d = max - min;
  2340.         s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
  2341.         switch (max) {
  2342.             case r: h = ((g - b) / d + (g < b ? 6 : 0)); break;
  2343.             case g: h = ((b - r) / d + 2); break;
  2344.             case b: h = ((r - g) / d + 4); break;
  2345.         }
  2346.         h *= 60;
  2347.     }
  2348.  
  2349.     return {
  2350.         h: Math.round(h),
  2351.         s: Math.round(s * 100),
  2352.         l: Math.round(l * 100)
  2353.     };
  2354. }
  2355.  
  2356. function hslToHex(h, s, l) {
  2357.     s /= 100;
  2358.     l /= 100;
  2359.  
  2360.     const k = n => (n + h / 30) % 12;
  2361.     const a = s * Math.min(l, 1 - l);
  2362.     const f = n =>
  2363.         Math.round(255 * (l - a * Math.max(-1, Math.min(k(n) - 3, Math.min(9 - k(n), 1)))));
  2364.  
  2365.     return `#${[f(0), f(8), f(4)].map(x => x.toString(16).padStart(2, '0')).join('')}`;
  2366. }
  2367.  
  2368. function clamp(value, min, max) {
  2369.     return Math.min(max, Math.max(min, value));
  2370. }
  2371.  
  2372. function findClosestSWColor(targetHex) {
  2373.     let bestMatch = null;
  2374.     let bestDistance = Infinity;
  2375.  
  2376.     for (const color of colorsData) {
  2377.         const dist = colorDistance(`#${color.hex}`, targetHex);
  2378.         if (dist < bestDistance) {
  2379.             bestDistance = dist;
  2380.             bestMatch = color;
  2381.         }
  2382.     }
  2383.  
  2384.     return bestMatch;
  2385. }
  2386.  
  2387. function colorDistance(hex1, hex2) {
  2388.     const rgb1 = hexToRGB(hex1);
  2389.     const rgb2 = hexToRGB(hex2);
  2390.     return Math.sqrt(
  2391.         Math.pow(rgb1.r - rgb2.r, 2) +
  2392.         Math.pow(rgb1.g - rgb2.g, 2) +
  2393.         Math.pow(rgb1.b - rgb2.b, 2)
  2394.     );
  2395. }
  2396.  
  2397. function hexToRGB(hex) {
  2398.     hex = hex.replace(/^#/, "");
  2399.     if (hex.length === 3) hex = hex.split('').map(c => c + c).join('');
  2400.     const bigint = parseInt(hex, 16);
  2401.     return { r: (bigint >> 16) & 255, g: (bigint >> 8) & 255, b: (bigint & 255) };
  2402. }
  2403. // Reusable listener setup
  2404. const setupPrintListener = () => {
  2405.     const tryAttachListener = (attempt = 1, maxAttempts = 10) => {
  2406.         const printButton = document.getElementById("printButton");
  2407.         console.log(`Print listener - Attempt ${attempt} - Looking for printButton: ${printButton ? "Found" : "Not found"}`);
  2408.  
  2409.         if (printButton) {
  2410.             const newButton = printButton.cloneNode(true);
  2411.             printButton.parentNode.replaceChild(newButton, printButton);
  2412.  
  2413.             newButton.addEventListener("click", async () => {
  2414.                 console.log("Print preview triggered");
  2415.                 const result = await generatePrintPreview();
  2416.                 if (!result) {
  2417.                     console.error("Print preview - Failed to generate output");
  2418.                 }
  2419.             });
  2420.             console.log("Print listener attached");
  2421.         } else if (attempt < maxAttempts) {
  2422.             console.warn(`Print button not found, retrying (${attempt}/${maxAttempts})`);
  2423.             setTimeout(() => tryAttachListener(attempt + 1, maxAttempts), 500);
  2424.         } else {
  2425.             console.error("Print button not found after max attempts");
  2426.         }
  2427.     };
  2428.  
  2429.     console.log("Print listener - Initial DOM state:", document.readyState);
  2430.     console.log("Print listener - Pattern preview wrapper:", document.getElementById("patternPreviewWrapper"));
  2431.  
  2432.     if (document.readyState === "complete" || document.readyState === "interactive") {
  2433.         tryAttachListener();
  2434.     } else {
  2435.         document.addEventListener("DOMContentLoaded", () => {
  2436.             console.log("Print listener - DOMContentLoaded fired");
  2437.             tryAttachListener();
  2438.         });
  2439.     }
  2440. };
  2441.  
  2442.  
  2443.     const toInitialCaps = (str) =>
  2444.         str
  2445.             .toLowerCase()
  2446.             .replace(/\.\w+$/, '') // Remove file extensions like .jpg, .png, etc.
  2447.             .replace(/-\d+x\d+$|-variant$/i, '') // Remove suffixes like -24x24, -variant
  2448.             .replace(/_/g, ' ') // Replace underscores with spaces
  2449.             .split(/[\s-]+/) // Split on spaces and hyphens
  2450.             .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
  2451.             .join(" ");
  2452.  
  2453.         const stripSWNumber = (colorName) => {
  2454.         return colorName.replace(/(SW|SC)\d+\s*/, '').trim(); // Removes "SW" followed by digits and optional space
  2455.     };
  2456.  
  2457. const getContrastClass = (hex) => {
  2458.     // console.trace("getContrastClass received:", hex);
  2459.  
  2460.     if (typeof hex !== "string" || !hex.startsWith("#") || hex.length < 7) {
  2461.         console.warn("โš ๏ธ Invalid hex value in getContrastClass:", hex);
  2462.         return "text-black"; // or choose a safe default
  2463.     }
  2464.  
  2465.     const r = parseInt(hex.slice(1, 3), 16);
  2466.     const g = parseInt(hex.slice(3, 5), 16);
  2467.     const b = parseInt(hex.slice(5, 7), 16);
  2468.     const brightness = (r * 299 + g * 587 + b * 114) / 1000;
  2469.     return brightness > 128 ? "text-black" : "text-white";
  2470. };
  2471.  
  2472.  
  2473. async function drawFurnitureLayer(ctx, imagePath, options = {}) {
  2474.     console.log("๐Ÿ” drawFurnitureLayer ENTRY:");
  2475.     console.log("  imagePath received:", imagePath);
  2476.     console.log("  Is sofa base?", imagePath?.includes('sofa-capitol-base'));
  2477.     console.log("  Is ferns pattern?", imagePath?.includes('ferns'));
  2478.  
  2479.     const {
  2480.         tintColor = null,
  2481.         isMask = false,
  2482.         opacity = 1.0,
  2483.         blendMode = "source-over",
  2484.         zoomState = null,
  2485.         highRes = false
  2486.     } = options;
  2487.    
  2488.     const width = 600;
  2489.     const height = 450;
  2490.  
  2491.     // โœ… Scale up for high resolution pattern rendering
  2492.     const renderScale = highRes ? 2 : 1;
  2493.     const renderWidth = width * renderScale;
  2494.     const renderHeight = height * renderScale;
  2495.  
  2496.    
  2497.     // โœ… Use passed zoom state if provided, otherwise fall back to global
  2498.     const activeZoomState = zoomState || {
  2499.         scale: furnitureViewSettings.scale,
  2500.         offsetX: furnitureViewSettings.offsetX,
  2501.         offsetY: furnitureViewSettings.offsetY,
  2502.         isZoomed: furnitureViewSettings.isZoomed
  2503.     };
  2504.    
  2505.     const { scale, offsetX, offsetY } = activeZoomState;
  2506.    
  2507.     console.log(`๐Ÿ” drawFurnitureLayer DEBUG for: ${imagePath.split('/').pop()}`);
  2508.     console.log(`   ๐Ÿ“Š ZOOM STATE: scale=${scale}, offset=(${offsetX.toFixed(1)}, ${offsetY.toFixed(1)})`);
  2509.     console.log(`   ๐Ÿ”’ Using ${zoomState ? 'PASSED' : 'GLOBAL'} zoom state`);
  2510.     console.log(`   Canvas size: ${width}x${height}`);
  2511.    
  2512.     try {
  2513.         const img = await loadImage(imagePath);
  2514.         if (!img) {
  2515.             console.error("โŒ Failed to load image:", imagePath);
  2516.             return;
  2517.         }
  2518.        
  2519.         console.log(`   Original image: ${img.naturalWidth}x${img.naturalHeight}`);
  2520.         if (highRes) console.log(`   ๐Ÿ” High-res rendering: ${renderWidth}x${renderHeight}`);
  2521.        
  2522.         const scaledWidth = img.naturalWidth * scale;  // Keep original logic
  2523.         const scaledHeight = img.naturalHeight * scale; // Keep original logic
  2524.  
  2525.         let drawX = (renderWidth / 2) - (scaledWidth / 2) + (offsetX * renderScale);
  2526.         let drawY = (renderHeight / 2) - (scaledHeight / 2) + (offsetY * renderScale);
  2527.        
  2528.        
  2529.         console.log(`   Draw position: (${drawX.toFixed(1)}, ${drawY.toFixed(1)})`);
  2530.        
  2531.         // Create working canvas at render resolution
  2532.         const tempCanvas = document.createElement("canvas");
  2533.         tempCanvas.width = renderWidth;
  2534.         tempCanvas.height = renderHeight;
  2535.         const tempCtx = tempCanvas.getContext("2d");
  2536.        
  2537.         if (isMask && tintColor) {
  2538.             // โœ… CORRECTED WALL MASK LOGIC
  2539.             console.log(`   ๐ŸŽญ Processing wall mask with color ${tintColor}`);
  2540.            
  2541.             // First, draw the mask image to get its alpha values
  2542.             const maskCanvas = document.createElement("canvas");
  2543.             maskCanvas.width = width;
  2544.             maskCanvas.height = height;
  2545.             const maskCtx = maskCanvas.getContext("2d");
  2546.            
  2547.             // Draw the scaled mask image
  2548.             maskCtx.drawImage(img, drawX, drawY, scaledWidth, scaledHeight);
  2549.            
  2550.             // Get the mask pixel data
  2551.             let maskImageData;
  2552.             try {
  2553.                 maskImageData = maskCtx.getImageData(0, 0, width, height);
  2554.             } catch (e) {
  2555.                 console.warn("โš ๏ธ Canvas tainted, falling back to simple draw for mask processing:", e.message);
  2556.                 tempCtx.drawImage(img, drawX, drawY, scaledWidth, scaledHeight);
  2557.                 ctx.drawImage(tempCanvas, 0, 0);
  2558.                 return;
  2559.             }
  2560.             const maskData = maskImageData.data;
  2561.            
  2562.             // Create output canvas with the tint color
  2563.             const outputImageData = tempCtx.createImageData(width, height);
  2564.             const outputData = outputImageData.data;
  2565.  
  2566.             console.log("๐ŸŽจ TINTING DEBUG:");
  2567.             console.log("  Image being tinted:", imagePath?.split('/').pop());
  2568.             console.log("  tintColor parameter:", tintColor);
  2569.             console.log("  Is sofa base:", imagePath?.includes('sofa-capitol-base'));
  2570.  
  2571.            
  2572.             // Parse tint color
  2573.             const hex = tintColor.replace("#", "");
  2574.             const r = parseInt(hex.substring(0, 2), 16);
  2575.             const g = parseInt(hex.substring(2, 4), 16);
  2576.             const b = parseInt(hex.substring(4, 6), 16);
  2577.            
  2578.             console.log("  Parsed RGB:", r, g, b);
  2579.             console.log("  Should be Cottage Linen RGB: 240, 240, 233");
  2580.  
  2581.             console.log(`   ๐ŸŽจ Tint color RGB: (${r}, ${g}, ${b})`);
  2582.            
  2583.             // Apply mask: white areas in mask = wall color, black areas = transparent
  2584.             for (let i = 0; i < maskData.length; i += 4) {
  2585.                 const maskR = maskData[i];
  2586.                 const maskG = maskData[i + 1];
  2587.                 const maskB = maskData[i + 2];
  2588.                
  2589.                 // Calculate mask intensity (how white the pixel is)
  2590.                 const maskIntensity = (maskR + maskG + maskB) / 3;
  2591.                
  2592.                 if (maskIntensity > 128) {
  2593.                     // White area in mask = apply wall color
  2594.                     outputData[i] = r;
  2595.                     outputData[i + 1] = g;
  2596.                     outputData[i + 2] = b;
  2597.                     outputData[i + 3] = 128; // Fully opaque
  2598.                 } else {
  2599.                     // Black area in mask = transparent (let room background show through)
  2600.                     outputData[i] = 0;
  2601.                     outputData[i + 1] = 0;
  2602.                     outputData[i + 2] = 0;
  2603.                     outputData[i + 3] = 0; // Fully transparent
  2604.                 }
  2605.             }
  2606.            
  2607.             // Put the processed image data to the temp canvas
  2608.             tempCtx.putImageData(outputImageData, 0, 0);
  2609.            
  2610.             console.log(`   โœ… Wall mask applied: white areas colored, black areas transparent`);
  2611.            
  2612.             } else if (tintColor) {
  2613.             if (imagePath?.includes('sofa-capitol-base') || imagePath?.includes('patterns/botanicals/')) {
  2614.                 console.log("๐ŸŽจ Using LUMINANCE-based logic for:", imagePath?.split('/').pop());
  2615.                
  2616.                 // Draw the image first
  2617.                 tempCtx.drawImage(img, drawX, drawY, scaledWidth, scaledHeight);
  2618.                
  2619.                 // Get image data
  2620.                 let imageData;
  2621.                 try {
  2622.                     imageData = tempCtx.getImageData(0, 0, renderWidth, renderHeight);
  2623.                 } catch (e) {
  2624.                     console.warn("โš ๏ธ Canvas tainted, falling back to simple tinting for luminance processing:", e.message);
  2625.                     // Fall back to simple tinting
  2626.                     tempCtx.fillStyle = tintColor;
  2627.                     tempCtx.fillRect(0, 0, renderWidth, renderHeight);
  2628.                     tempCtx.globalCompositeOperation = "destination-in";
  2629.                     tempCtx.drawImage(img, drawX, drawY, scaledWidth, scaledHeight);
  2630.                     tempCtx.globalCompositeOperation = "source-over";
  2631.                     ctx.drawImage(tempCanvas, 0, 0);
  2632.                     return;
  2633.                 }
  2634.                 const data = imageData.data;
  2635.                
  2636.                 // Parse tint color
  2637.                 const hex = tintColor.replace("#", "");
  2638.                 const rLayer = parseInt(hex.substring(0, 2), 16);
  2639.                 const gLayer = parseInt(hex.substring(2, 4), 16);
  2640.                 const bLayer = parseInt(hex.substring(4, 6), 16);
  2641.                
  2642.                 // โœ… USE LUMINANCE for both sofa base AND patterns
  2643.                 for (let i = 0; i < data.length; i += 4) {
  2644.                     const r = data[i];
  2645.                     const g = data[i + 1];
  2646.                     const b = data[i + 2];
  2647.                     const brightness = (r + g + b) / 3;
  2648.                    
  2649.                     if (brightness <= 5) {  // Pure black - transparent
  2650.                         data[i + 3] = 0;
  2651.                     } else {  // Non-black pixels - tint based on brightness
  2652.                         const alpha = brightness / 255;
  2653.                        
  2654.                         data[i] = rLayer;
  2655.                         data[i + 1] = gLayer;
  2656.                         data[i + 2] = bLayer;
  2657.                         data[i + 3] = Math.round(alpha * 255);
  2658.                     }
  2659.                 }
  2660.                
  2661.                 tempCtx.putImageData(imageData, 0, 0);
  2662.                
  2663.             } else {
  2664.                 // Keep original alpha-based logic for other elements (if any)
  2665.                 tempCtx.fillStyle = tintColor;
  2666.                 tempCtx.fillRect(0, 0, width, height);
  2667.                
  2668.                 tempCtx.globalCompositeOperation = "destination-in";
  2669.                 tempCtx.drawImage(img, drawX, drawY, scaledWidth, scaledHeight);
  2670.                 tempCtx.globalCompositeOperation = "source-over";
  2671.             }
  2672.         }
  2673.  
  2674.         else {
  2675.             // Direct images - draw at calculated position and size
  2676.             tempCtx.drawImage(img, drawX, drawY, scaledWidth, scaledHeight);
  2677.             console.log(`   โœ… Direct image drawn at (${drawX.toFixed(1)}, ${drawY.toFixed(1)})`);
  2678.         }
  2679.        
  2680.         // Draw to main canvas
  2681.         ctx.save();
  2682.         ctx.globalAlpha = opacity;
  2683.         console.log("   ๐ŸŽจ Using NORMAL blend for", imagePath?.split('/').pop());
  2684.         ctx.globalCompositeOperation = blendMode; // Normal for everything else
  2685.  
  2686.         if (highRes) {
  2687.             // Scale down from high-res to normal resolution
  2688.             ctx.drawImage(tempCanvas, 0, 0, renderWidth, renderHeight, 0, 0, width, height);
  2689.             console.log(`   โœ… High-res layer scaled down and composited`);
  2690.         } else {
  2691.             ctx.drawImage(tempCanvas, 0, 0);
  2692.         }
  2693.         ctx.restore();        
  2694.         console.log(`   โœ… Layer composited to main canvas`);
  2695.        
  2696.     } catch (error) {
  2697.         console.error("โŒ Error in drawFurnitureLayer:", error);
  2698.     }
  2699. }
  2700.  
  2701. // Create a color input UI element
  2702. const createColorInput = (label, id, initialColor, isBackground = false) => {
  2703.     console.log(`Creating ${label} input, ID: ${id}, initialColor: ${initialColor}`);
  2704.    
  2705.     const container = document.createElement("div");
  2706.     container.className = "layer-input-container";
  2707.  
  2708.     const labelEl = document.createElement("div");
  2709.     labelEl.className = "layer-label";
  2710.     labelEl.textContent = label || "Unknown Layer";
  2711.  
  2712.     const colorCircle = document.createElement("div");
  2713.     colorCircle.className = "circle-input";
  2714.     colorCircle.id = `${id}Circle`;
  2715.     const cleanInitialColor = (initialColor || "Snowbound").replace(/^(SW|SC)\d+\s*/i, "").trim();
  2716.     const colorValue = lookupColor(cleanInitialColor);
  2717.     console.log(`Setting ${label} circle background to: ${colorValue}`);
  2718.     colorCircle.style.backgroundColor = colorValue;
  2719.  
  2720.     const input = document.createElement("input");
  2721.     input.type = "text";
  2722.     input.className = "layer-input";
  2723.     input.id = id;
  2724.     input.placeholder = `Enter ${label ? label.toLowerCase() : 'layer'} color`;
  2725.     input.value = toInitialCaps(cleanInitialColor);
  2726.     console.log(`Setting ${label} input value to: ${input.value}`);
  2727.  
  2728.     container.append(labelEl, colorCircle, input);
  2729.    
  2730.  
  2731.  
  2732.     const updateColor = () => {
  2733.         console.log(`updateColor called for ${label}, input value: ${input.value}`);
  2734.         const formatted = toInitialCaps(input.value.trim());
  2735.         if (!formatted) {
  2736.             input.value = toInitialCaps(cleanInitialColor);
  2737.             colorCircle.style.backgroundColor = colorValue;
  2738.             console.log(`${label} input restored to initial color: ${colorValue}`);
  2739.         } else {
  2740.             const hex = lookupColor(formatted) || "#FFFFFF";
  2741.             if (hex === "#FFFFFF" && formatted !== "Snowbound") {
  2742.                 input.value = toInitialCaps(cleanInitialColor);
  2743.                 colorCircle.style.backgroundColor = colorValue;
  2744.                 console.log(`${label} input restored to initial color due to invalid color: ${colorValue}`);
  2745.             } else {
  2746.                 input.value = formatted;
  2747.                 colorCircle.style.backgroundColor = hex;
  2748.                 console.log(`${label} input updated to: ${hex}`);
  2749.             }
  2750.         }
  2751.  
  2752.         const layerIndex = appState.currentLayers.findIndex(layer => layer.label === label);
  2753.         if (layerIndex !== -1) {
  2754.             appState.currentLayers[layerIndex].color = input.value;
  2755.  
  2756.             console.log("๐ŸŽฏ COLOR UPDATE DEBUG:");
  2757.             console.log(`  Changed input: ${label} (index ${layerIndex})`);
  2758.             console.log(`  New value: ${input.value}`);
  2759.             console.log("  Current layer structure after update:");
  2760.             appState.currentLayers.forEach((layer, i) => {
  2761.                 console.log(`    ${i}: ${layer.label} = "${layer.color}"`);
  2762.             });
  2763.  
  2764.  
  2765.             console.log(`Updated appState.currentLayers[${layerIndex}].color to: ${input.value}`);
  2766.         }
  2767.  
  2768.         const isFurniturePattern = appState.currentPattern?.isFurniture || false;
  2769.  
  2770.         // Check if we're in fabric mode - render both fabric mockup and pattern preview
  2771.         if (appState.isInFabricMode) {
  2772.             console.log("๐Ÿงต Color changed in fabric mode - calling both renderFabricMockup() and updatePreview()");
  2773.             renderFabricMockup();
  2774.             updatePreview(); // Also update the pattern preview on the left
  2775.         } else {
  2776.             // Regular furniture or pattern mode
  2777.             updatePreview();
  2778.             updateRoomMockup();
  2779.         }
  2780.         populateCoordinates();
  2781.     };
  2782.  
  2783.     // Restore original event listeners
  2784.     input.addEventListener("blur", updateColor);
  2785.     input.addEventListener("keydown", (e) => {
  2786.         if (e.key === "Enter") updateColor();
  2787.     });
  2788.  
  2789.     // Restore original click handler
  2790.     console.log(`Attaching click handler to ${label} color circle, ID: ${colorCircle.id}`);
  2791.         colorCircle.addEventListener("click", () => {
  2792.         // Check if we're in coordinate mode (back button exists) - exit coordinate mode
  2793.         const coordinateBackButton = document.getElementById('backToPatternLink');
  2794.        
  2795.         if (coordinateBackButton) {
  2796.             console.log(`๐Ÿ”„ Color circle clicked in coordinate mode - triggering back to pattern then selecting layer`);
  2797.             coordinateBackButton.click(); // Trigger the coordinate back button
  2798.             // Pass through the click after returning to pattern mode
  2799.             setTimeout(() => {
  2800.                 appState.lastSelectedLayer = {
  2801.                     input,
  2802.                     circle: colorCircle,
  2803.                     label,
  2804.                     isBackground
  2805.                 };
  2806.                 highlightActiveLayer(colorCircle);
  2807.                 console.log(`โœ… Layer selected after returning from coordinate mode: ${label}`);
  2808.             }, 50);
  2809.             return;
  2810.         }
  2811.        
  2812.         // In furniture mode, allow normal color changes - do NOT exit furniture mode
  2813.         const furnitureBackButton = document.getElementById('backToPatternsBtn');
  2814.         if (furnitureBackButton) {
  2815.             console.log(`๐ŸŽจ Color circle clicked in furniture mode - changing color while staying in furniture mode: ${label}`);
  2816.             // Continue with normal color selection behavior below
  2817.         }
  2818.        
  2819.         // Normal color circle behavior
  2820.         appState.lastSelectedLayer = {
  2821.             input,
  2822.             circle: colorCircle,
  2823.             label,
  2824.             isBackground
  2825.         };
  2826.         highlightActiveLayer(colorCircle);
  2827.         console.log(`Clicked ${label} color circle`);
  2828.     });
  2829.  
  2830.  
  2831.     return {
  2832.         container,
  2833.         input,
  2834.         circle: colorCircle,
  2835.         label,
  2836.         isBackground
  2837.     };
  2838.  
  2839. };
  2840.  
  2841.  
  2842. // Populate curated colors in header
  2843. function populateCuratedColors(colors) {
  2844.   console.log("๐ŸŽจ populateCuratedColors called with colors:", colors?.length);
  2845.   console.log("๐Ÿ” curatedColorsContainer element:", dom.curatedColorsContainer);
  2846.  
  2847.   if (!dom.curatedColorsContainer) {
  2848.     console.error("โŒ curatedColorsContainer not found in DOM");
  2849.     console.log("๐Ÿ” Available DOM elements:", Object.keys(dom));
  2850.     return;
  2851.   }
  2852.  
  2853.   if (!colors || !colors.length) {
  2854.     console.warn("โš ๏ธ No curated colors provided, colors array:", colors);
  2855.     return;
  2856.   }
  2857.  
  2858.   console.log("โœ… About to populate", colors.length, "curated colors");
  2859.  
  2860.   dom.curatedColorsContainer.innerHTML = "";
  2861.  
  2862.   // ๐ŸŽŸ๏ธ Run The Ticket Button
  2863.   const ticketCircle = document.createElement("div");
  2864.   ticketCircle.id = "runTheTicketCircle";
  2865.   ticketCircle.className = "curated-color-circle cursor-pointer border-2";
  2866.   ticketCircle.style.backgroundColor = "black";
  2867.  
  2868.   const ticketLabel = document.createElement("span");
  2869.   ticketLabel.className = "text-xs font-bold text-white text-center whitespace-pre-line font-special-elite";
  2870.   ticketLabel.textContent = appState.activeTicketNumber
  2871.     ? `TICKET\n${appState.activeTicketNumber}`
  2872.     : "RUN\nTHE\nTICKET";
  2873.  
  2874.   ticketCircle.appendChild(ticketLabel);
  2875.   ticketCircle.addEventListener("click", () => {
  2876.     const ticketNumber = prompt("๐ŸŽŸ๏ธ Enter the Sherwin-Williams Ticket Number:");
  2877.     if (ticketNumber) runStaticTicket(ticketNumber.trim());
  2878.   });
  2879.   dom.curatedColorsContainer.appendChild(ticketCircle);
  2880.  
  2881.   // ๐ŸŽจ Add curated color swatches
  2882.   colors.forEach(label => {
  2883.     if (!Array.isArray(appState.colorsData)) {
  2884.   console.error("โŒ appState.colorsData is not available or not an array");
  2885.   return;
  2886. }
  2887.  
  2888. const found = appState.colorsData.find(c =>
  2889.  
  2890.       label.toLowerCase().includes(c.sw_number?.toLowerCase()) ||
  2891.       label.toLowerCase().includes(c.color_name?.toLowerCase())
  2892.     );
  2893.  
  2894.     if (!found || !found.hex) {
  2895.       console.warn("โš ๏ธ Missing hex for curated color:", label);
  2896.       return;
  2897.     }
  2898.  
  2899.     const hex = `#${found.hex}`;
  2900.     const circle = document.createElement("div");
  2901.     circle.className = "curated-color-circle cursor-pointer";
  2902.     circle.style.backgroundColor = hex;
  2903.  
  2904.     const text = document.createElement("span");
  2905.     text.className = `text-xs font-bold text-center ${getContrastClass(hex)} whitespace-pre-line`;
  2906.     text.textContent = `${found.sw_number?.toUpperCase()}\n${toInitialCaps(found.color_name)}`;
  2907.  
  2908.     circle.appendChild(text);
  2909.     circle.addEventListener("click", () => {
  2910.       const selectedLayer = appState.lastSelectedLayer;
  2911.       if (!selectedLayer) return alert("Please select a layer first.");
  2912.  
  2913.       selectedLayer.input.value = toInitialCaps(found.color_name);
  2914.       selectedLayer.circle.style.backgroundColor = hex;
  2915.  
  2916.       const i = appState.currentLayers.findIndex(l => l.label === selectedLayer.label);
  2917.       if (i !== -1) appState.currentLayers[i].color = found.color_name;
  2918.  
  2919.       const j = appState.layerInputs.findIndex(li => li.label === selectedLayer.label);
  2920.       if (j !== -1) {
  2921.         appState.layerInputs[j].input.value = toInitialCaps(found.color_name);
  2922.         appState.layerInputs[j].circle.style.backgroundColor = hex;
  2923.       }
  2924.  
  2925.       appState.lastSelectedColor = { name: found.color_name, hex };
  2926.       updateDisplays();
  2927.     });
  2928.  
  2929.     dom.curatedColorsContainer.appendChild(circle);
  2930.   });
  2931.  
  2932.   console.log("โœ… Curated colors populated:", colors.length);
  2933. }
  2934.  
  2935. function getLayerMappingForPreview(isFurnitureCollection) {
  2936.     if (isFurnitureCollection) {
  2937.         return {
  2938.             type: 'furniture',
  2939.             patternStartIndex: 2,      // Pattern layers start at index 2  
  2940.             backgroundIndex: 1,        // Sofa base = pattern background (index 1)
  2941.             wallIndex: 0               // Wall color (index 0)
  2942.         };
  2943.     } else {
  2944.         return {
  2945.             type: 'standard',
  2946.             patternStartIndex: 1,      // Pattern layers start at index 1
  2947.             backgroundIndex: 0,        // True background
  2948.             wallIndex: null            // No wall color
  2949.         };
  2950.     }
  2951. }
  2952.  
  2953. function validateLayerMapping() {
  2954.     const isFurnitureCollection = appState.selectedCollection?.furnitureType != null || appState.furnitureMode === true;
  2955.     const mapping = getLayerMappingForPreview(isFurnitureCollection);
  2956.    
  2957.     console.log("๐Ÿ” LAYER MAPPING VALIDATION (WITH WALL COLOR):");
  2958.     console.log("  Collection type:", isFurnitureCollection ? "furniture" : "standard");
  2959.     console.log("  Total inputs:", appState.currentLayers.length);
  2960.     console.log("  Pattern start index:", mapping.patternStartIndex);
  2961.     console.log("  Background/Sofa base index:", mapping.backgroundIndex);
  2962.     console.log("  Wall index:", mapping.wallIndex);
  2963.    
  2964.     console.log("  Layer assignments:");
  2965.     appState.currentLayers.forEach((layer, index) => {
  2966.         let usage = "unused";
  2967.         if (index === mapping.wallIndex) {
  2968.             usage = "wall color (via mask)";
  2969.         } else if (index === mapping.backgroundIndex) {
  2970.             if (isFurnitureCollection) {
  2971.                 usage = "sofa base + pattern background";
  2972.             } else {
  2973.                 usage = "pattern background";
  2974.             }
  2975.         } else if (index >= mapping.patternStartIndex) {
  2976.             usage = `pattern layer ${index - mapping.patternStartIndex}`;
  2977.         }
  2978.        
  2979.         console.log(`    ${index}: ${layer.label} = "${layer.color}" (${usage})`);
  2980.     });
  2981.  
  2982.     // Show the mapping clearly
  2983.     if (isFurnitureCollection) {
  2984.         console.log("๐Ÿ”„ FURNITURE COLLECTION MAPPING (WITH WALL MASK):");
  2985.         console.log("  Pattern Preview:");
  2986.         console.log(`    Background โ† Input ${mapping.backgroundIndex} (${appState.currentLayers[mapping.backgroundIndex]?.label})`);
  2987.         for (let i = 0; i < (appState.currentLayers.length - mapping.patternStartIndex); i++) {
  2988.             const inputIndex = mapping.patternStartIndex + i;
  2989.             if (appState.currentLayers[inputIndex]) {
  2990.                 console.log(`    Pattern Layer ${i} โ† Input ${inputIndex} (${appState.currentLayers[inputIndex].label})`);
  2991.             }
  2992.         }
  2993.         console.log("  Furniture Mockup:");
  2994.         console.log("    Room Scene โ† sofa-capitol.png");
  2995.         console.log(`    Wall Areas โ† Input ${mapping.wallIndex} (${appState.currentLayers[mapping.wallIndex]?.label}) via wall mask`);
  2996.         console.log(`    Sofa Base โ† Input ${mapping.backgroundIndex} (${appState.currentLayers[mapping.backgroundIndex]?.label})`);
  2997.         for (let i = 0; i < (appState.currentLayers.length - mapping.patternStartIndex); i++) {
  2998.             const inputIndex = mapping.patternStartIndex + i;
  2999.             if (appState.currentLayers[inputIndex]) {
  3000.                 console.log(`    Pattern Layer ${i} โ† Input ${inputIndex} (${appState.currentLayers[inputIndex].label})`);
  3001.             }
  3002.         }
  3003.     }
  3004. }
  3005.  
  3006.  
  3007. function insertTicketIndicator(ticketNumber) {
  3008.     const existing = document.getElementById("ticketIndicator");
  3009.     if (existing) {
  3010.         existing.innerHTML = `TICKET<br>${ticketNumber}`;
  3011.         return;
  3012.     }
  3013.  
  3014.     const indicator = document.createElement("div");
  3015.     indicator.id = "ticketIndicator";
  3016.     indicator.className = "w-20 h-20 rounded-full flex items-center justify-center text-center text-xs font-bold text-gray-800";
  3017.     indicator.style.backgroundColor = "#e5e7eb"; // Tailwind gray-200
  3018.     indicator.style.marginRight = "8px";
  3019.     indicator.innerHTML = `TICKET<br>${ticketNumber}`;
  3020.  
  3021.     dom.curatedColorsContainer.prepend(indicator);
  3022. }
  3023.  
  3024. function promptTicketNumber() {
  3025.     const input = prompt("Enter Sherwin-Williams ticket number (e.g., 280):");
  3026.     const ticketNum = parseInt(input?.trim());
  3027.     if (isNaN(ticketNum)) {
  3028.         alert("Please enter a valid numeric ticket number.");
  3029.         return;
  3030.     }
  3031.     runStaticTicket(ticketNum);
  3032. }
  3033.  
  3034. function runTheTicket(baseColor) {
  3035.     console.log("รฐลธลฝลธรฏยธย Running the Ticket for:", baseColor);
  3036.  
  3037.     if (!isAppReady) {
  3038.         console.warn("โš ๏ธ App is not ready yet. Ignoring runTheTicket call.");
  3039.         alert("Please wait while the app finishes loading.");
  3040.         return;
  3041.     }
  3042.  
  3043.     if (!baseColor || !baseColor.hex) {
  3044.         console.warn("รขยล’ No base color provided to runTheTicket.");
  3045.         return;
  3046.     }
  3047.  
  3048.     if (!Array.isArray(appState.colorsData) || appState.colorsData.length === 0) {
  3049.         console.warn("Xยธย Sherwin-Williams colors not loaded yet.");
  3050.         alert("Color data is still loading. Please try again shortly.");
  3051.         return;
  3052.     }
  3053.  
  3054.     const baseHSL = hexToHSL(baseColor.hex);
  3055.     if (!baseHSL) {
  3056.         console.error("X Failed to convert base HEX to HSL.");
  3057.         return;
  3058.     }
  3059.  
  3060.     console.log("+ Base color HSL:", baseHSL);
  3061.  
  3062.     const swColors = appState.colorsData
  3063.         .filter(c => c.hex && c.name)
  3064.         .map(c => ({
  3065.             name: c.name,
  3066.             hex: c.hex,
  3067.             hsl: hexToHSL(c.hex)
  3068.         }));
  3069.  
  3070.     console.log("** Total SW Colors to search:", swColors.length);
  3071.  
  3072.     const scored = swColors
  3073.         .map(c => {
  3074.             const hueDiff = Math.abs(baseHSL.h - c.hsl.h);
  3075.             const satDiff = Math.abs(baseHSL.s - c.hsl.s);
  3076.             const lightDiff = Math.abs(baseHSL.l - c.hsl.l);
  3077.             return {
  3078.                 ...c,
  3079.                 score: hueDiff + satDiff * 0.5 + lightDiff * 0.8
  3080.             };
  3081.         })
  3082.         .sort((a, b) => a.score - b.score)
  3083.         .slice(0, appState.currentLayers.length);
  3084.  
  3085.     console.log("รฐลธลฝยฏ Top Ticket matches:", scored);
  3086.  
  3087.     if (!Array.isArray(appState.layerInputs) || appState.layerInputs.length === 0) {
  3088.         console.warn("รขยล’ No layer inputs available. Cannot apply ticket.");
  3089.         return;
  3090.     }
  3091.  
  3092.     scored.forEach((ticketColor, idx) => {
  3093.         const inputSet = appState.layerInputs[idx];
  3094.         if (!inputSet || !inputSet.input || !inputSet.circle) {
  3095.             console.warn(`รขยล’ Missing input or circle at index ${idx}`);
  3096.             return;
  3097.         }
  3098.  
  3099.         const formatted = toInitialCaps(ticketColor.name);
  3100.         inputSet.input.value = formatted;
  3101.         inputSet.circle.style.backgroundColor = ticketColor.hex;
  3102.         appState.currentLayers[idx].color = formatted;
  3103.  
  3104.         console.log(`รฐลธลฝยฏ Layer ${idx + 1} set to ${formatted} (${ticketColor.hex})`);
  3105.     });
  3106.  
  3107.     insertTicketIndicator(ticketNumber);
  3108.  
  3109.     updateDisplays();
  3110.     console.log("โœ… Ticket run complete.");
  3111. }
  3112.  
  3113. function runStaticTicket(ticketNumber) {
  3114.     console.log(`รฐลธลฝยซ Static Ticket Requested: ${ticketNumber}`);
  3115.  
  3116.     if (!Array.isArray(appState.colorsData) || appState.colorsData.length === 0) {
  3117.         alert("Color data not loaded yet.");
  3118.         return;
  3119.     }
  3120.  
  3121.     const ticketColors = [];
  3122.     for (let i = 1; i <= 7; i++) {
  3123.         const locatorId = `${ticketNumber}-C${i}`;
  3124.         const color = appState.colorsData.find(c => c.locator_id?.toUpperCase() === locatorId.toUpperCase());
  3125.         if (color) {
  3126.             const displayName = `${color.sw_number?.toUpperCase() || ""} ${toInitialCaps(color.color_name)}`;
  3127.             ticketColors.push(displayName.trim());
  3128.         }
  3129.     }
  3130.  
  3131.     if (ticketColors.length === 0) {
  3132.         alert(`No colors found for ticket ${ticketNumber}`);
  3133.         return;
  3134.     }
  3135.  
  3136.     appState.curatedColors = ticketColors;
  3137.     appState.activeTicketNumber = ticketNumber; // รฐลธโ€ โ€ข Track it for label update
  3138.     populateCuratedColors(ticketColors);
  3139.  
  3140.     console.log(`รฐลธลฝยฏ Loaded ticket ${ticketNumber} with ${ticketColors.length} colors`);
  3141. }
  3142.  
  3143.  
  3144. async function initializeApp() {
  3145.     console.log("๐Ÿš€ Starting app...");
  3146.    
  3147.     // Validate DOM elements first
  3148.     validateDOMElements();
  3149.    
  3150.     // โœ… Step 1: Load Sherwin-Williams Colors
  3151.     await loadColors();
  3152.     console.log("โœ… Colors loaded:", appState.colorsData.length);
  3153.  
  3154.     try {
  3155.         // โœ… Step 2: Load Collections
  3156.         // Check if data is embedded in window object (Shopify mode)
  3157.         let data;
  3158.         if (window.ColorFlexData && window.ColorFlexData.collections) {
  3159.             console.log("๐ŸŽฏ Using embedded ColorFlex data");
  3160.             data = { collections: window.ColorFlexData.collections };
  3161.         } else {
  3162.             console.log("๐Ÿ“ Loading collections from Shopify assets");
  3163.             const collectionsUrl = window.ColorFlexAssets?.collectionsUrl || "/assets/collections.json";
  3164.             const response = await fetch(collectionsUrl, {
  3165.                 method: 'GET',
  3166.                 cache: "no-store",
  3167.                 headers: {
  3168.                     'Content-Type': 'application/json',
  3169.                 }
  3170.             });
  3171.             if (!response.ok) throw new Error(`Failed to fetch collections: ${response.status}`);
  3172.             data = await response.json();
  3173.         }
  3174.  
  3175.         // ADD THIS DEBUG:
  3176.         console.log("๐Ÿ” Raw JSON collections loaded:", data.collections.length);
  3177.         const farmhouseCollection = data.collections.find(c => c.name === "farmhouse");
  3178.         console.log("๐Ÿ” Raw farmhouse collection:", farmhouseCollection);
  3179.         console.log("๐Ÿ” Raw farmhouse elements:", farmhouseCollection?.elements);
  3180.  
  3181.  
  3182.         if (!data.collections?.length) {
  3183.             console.error("X No collections found in collections.json");
  3184.             dom.collectionHeader.textContent = "No Collections Available";
  3185.             dom.preview.innerHTML = "<p>No collections available. Please run the data import script.</p>";
  3186.             return;
  3187.         }
  3188.  
  3189.         // โœ… Step 3: Save collections once
  3190.         if (!appState.collections.length) {
  3191.             appState.collections = data.collections;
  3192.             console.log("โœ… Collections loaded:", appState.collections.length);
  3193.         }
  3194.  
  3195.         // โœ… Step 4: Select collection via Shopify integration, URL param, or fallback
  3196.         const urlParams = new URLSearchParams(window.location.search);
  3197.         const urlCollectionName = urlParams.get("collection")?.trim();
  3198.        
  3199.         console.log("๐Ÿ” COLLECTION SELECTION DEBUG:");
  3200.         console.log("  URL collection param:", urlCollectionName);
  3201.         console.log("  Shopify target collection:", window.appState?.selectedCollection);
  3202.         console.log("  Shopify target pattern:", window.appState?.targetPattern?.name);
  3203.         console.log("  Available collections:", appState.collections.map(c => c.name));
  3204.         console.log("  Total collections loaded:", appState.collections.length);
  3205.        
  3206.         // Priority 1: Use Shopify-detected collection (from product page integration)
  3207.         let collectionName = window.appState?.selectedCollection || urlCollectionName;
  3208.        
  3209.         let selectedCollection = appState.collections.find(
  3210.             c => c.name.trim().toLowerCase() === collectionName?.toLowerCase()
  3211.         ) || appState.collections[0];
  3212.        
  3213.         console.log("  Selected collection source:", window.appState?.selectedCollection ? "Shopify" : "URL");
  3214.         console.log("  Final collection:", selectedCollection?.name);
  3215.  
  3216.         if (!selectedCollection) {
  3217.             console.error("X No valid collection found.");
  3218.             return;
  3219.         }
  3220.  
  3221.         // โœ… Step 5: Set collection in appState
  3222.         appState.selectedCollection = selectedCollection;
  3223.         appState.lockedCollection = true;
  3224.         appState.curatedColors = selectedCollection.curatedColors || [];
  3225.         console.log("@ Selected Collection:", selectedCollection.name);
  3226.         console.log("@ Curated colors:", appState.curatedColors.length);
  3227.  
  3228.         // โœ… Step 6: Update UI header
  3229.         if (dom.collectionHeader) {
  3230.             dom.collectionHeader.textContent = toInitialCaps(selectedCollection.name);
  3231.         }
  3232.  
  3233.         // โœ… Step 7: Show curated color circles + ticket button
  3234.         populateCuratedColors(appState.curatedColors);
  3235.  
  3236.         // โœ… Step 8: Load target pattern or first pattern
  3237.         // Priority 1: Check URL pattern parameter
  3238.         let initialPattern = null;
  3239.         const urlPatternName = urlParams.get("pattern")?.trim();
  3240.         if (urlPatternName) {
  3241.             // First try to find pattern in selected collection
  3242.             initialPattern = selectedCollection.patterns.find(p =>
  3243.                 p.name?.toLowerCase() === urlPatternName.toLowerCase() ||
  3244.                 p.id === urlPatternName
  3245.             ) || selectedCollection.patterns.find(p =>
  3246.                 p.name?.toLowerCase().includes(urlPatternName.toLowerCase()) ||
  3247.                 urlPatternName.toLowerCase().includes(p.name?.toLowerCase())
  3248.             );
  3249.            
  3250.             // If pattern not found in selected collection, search all collections (DYNAMIC)
  3251.             if (!initialPattern) {
  3252.                 console.log("๐Ÿ” Pattern not found in selected collection, searching all collections dynamically...");
  3253.                 console.log(`๐Ÿ” Searching for pattern: "${urlPatternName}" across ${appState.collections.length} collections`);
  3254.                
  3255.                 for (const collection of appState.collections) {
  3256.                     console.log(`  ๐Ÿ” Checking collection: "${collection.name}" (${collection.patterns?.length || 0} patterns)`);
  3257.                     const foundPattern = collection.patterns?.find(p => {
  3258.                         const patternName = p.name?.toLowerCase() || '';
  3259.                         const patternId = p.id?.toLowerCase() || '';
  3260.                         const searchName = urlPatternName.toLowerCase();
  3261.                        
  3262.                         // Exact matches first
  3263.                         if (patternName === searchName || patternId === searchName) return true;
  3264.                        
  3265.                         // Partial matches
  3266.                         if (patternName.includes(searchName) || searchName.includes(patternName)) return true;
  3267.                        
  3268.                         // Handle special cases for known patterns
  3269.                         if (searchName === 'constantinople' && patternName.includes('constantinople')) return true;
  3270.                         if (searchName === 'istanbul' && patternName.includes('istanbul')) return true;
  3271.                        
  3272.                         return false;
  3273.                     });
  3274.                    
  3275.                     if (foundPattern) {
  3276.                         console.log(`๐ŸŽฏ FOUND: Pattern "${urlPatternName}" โ†’ "${foundPattern.name}" in collection "${collection.name}"`);
  3277.                         console.log(`๐Ÿ”„ Switching from collection "${selectedCollection.name}" to "${collection.name}"`);
  3278.                        
  3279.                         selectedCollection = collection;
  3280.                         appState.selectedCollection = selectedCollection;
  3281.                         appState.curatedColors = selectedCollection.curatedColors || [];
  3282.                         initialPattern = foundPattern;
  3283.                        
  3284.                         // Update UI to reflect correct collection
  3285.                         if (dom.collectionHeader) {
  3286.                             dom.collectionHeader.textContent = toInitialCaps(selectedCollection.name);
  3287.                         }
  3288.                         populateCuratedColors(appState.curatedColors);
  3289.                         break;
  3290.                     }
  3291.                 }
  3292.                
  3293.                 if (!initialPattern) {
  3294.                     console.warn(`โŒ Pattern "${urlPatternName}" not found in any collection`);
  3295.                 }
  3296.             }
  3297.             console.log("๐ŸŽฏ Using URL pattern parameter:", urlPatternName, "โ†’", initialPattern?.name, "in collection:", selectedCollection?.name);
  3298.         }
  3299.        
  3300.         // Priority 2: Use Shopify-detected target pattern
  3301.         if (!initialPattern && window.appState?.targetPattern) {
  3302.             initialPattern = selectedCollection.patterns.find(p =>
  3303.                 p.id === window.appState.targetPattern.id ||
  3304.                 p.name === window.appState.targetPattern.name
  3305.             );
  3306.             console.log("๐ŸŽฏ Using Shopify target pattern:", initialPattern?.name);
  3307.         }
  3308.        
  3309.         // Priority 3: Use first pattern as fallback
  3310.         if (!initialPattern) {
  3311.             initialPattern = selectedCollection.patterns[0];
  3312.             console.log("๐Ÿ“ Using first pattern as fallback:", initialPattern?.name);
  3313.         }
  3314.        
  3315.         const initialPatternId = initialPattern?.id;
  3316.         if (initialPatternId) {
  3317.             loadPatternData(selectedCollection, initialPatternId);  // โœ… Fixed: pass collection
  3318.         } else {
  3319.             console.warn("รขลก รฏยธย No patterns found for", selectedCollection.name);
  3320.         }
  3321.  
  3322.         // โœ… Step 9: Load thumbnails + setup print
  3323.         populatePatternThumbnails(selectedCollection.patterns);
  3324.         setupPrintListener();
  3325.  
  3326.         isAppReady = true;
  3327.         console.log("โœ… App is now fully ready.");
  3328.  
  3329.         function initializeInteractiveZoom() {
  3330.             // Set up interactive zoom when app is ready
  3331.             if (document.readyState === 'loading') {
  3332.                 document.addEventListener('DOMContentLoaded', addInteractiveZoom);
  3333.             } else {
  3334.                 addInteractiveZoom();
  3335.             }
  3336.         }
  3337.         // Call this when collections are loaded
  3338.         initializeInteractiveZoom();  // โ† Add this line right here
  3339.         initializeTryFurnitureFeature();
  3340.  
  3341.         console.log("Current state during app init:");
  3342. console.log("  furnitureConfig loaded:", !!furnitureConfig);
  3343. console.log("  appState.selectedCollection:", !!appState.selectedCollection);
  3344. console.log("  appState.collections:", !!appState.collections?.length);
  3345. console.log("  DOM ready:", document.readyState);
  3346.  
  3347.  
  3348.  
  3349.     } catch (error) {
  3350.         console.error("X Error loading collections:", error);
  3351.         dom.collectionHeader.textContent = "Error Loading Collection";
  3352.         dom.preview.innerHTML = "<p>Error loading data. Please try refreshing.</p>";
  3353.     }
  3354. }
  3355.  
  3356.  
  3357. // Ensure appState has a default
  3358. appState._selectedCollection = null;
  3359.  
  3360. // Run on initial load and refresh
  3361. window.addEventListener('load', () => {
  3362.     initializeApp().catch(error => console.error("Initialization failed:", error));
  3363. });
  3364.  
  3365. window.addEventListener('popstate', () => {
  3366.     initializeApp().catch(error => console.error("Refresh initialization failed:", error));
  3367. });
  3368.  
  3369. // Populate pattern thumbnails in sidebar
  3370. function populatePatternThumbnails(patterns) {
  3371.     console.log("populatePatternThumbnails called with patterns:", patterns);
  3372.     if (!dom.collectionThumbnails) {
  3373.         console.error("collectionThumbnails not found in DOM");
  3374.         return;
  3375.     }
  3376.     if (!Array.isArray(patterns)) {
  3377.         console.error("Patterns is not an array:", patterns);
  3378.         return;
  3379.     }
  3380.  
  3381.     const validPatterns = patterns.filter(p => p && typeof p === 'object' && p.name);
  3382.     if (!validPatterns.length) {
  3383.         console.warn("No valid patterns to display");
  3384.         dom.collectionThumbnails.innerHTML = "<p>No patterns available.</p>";
  3385.         return;
  3386.     }
  3387.  
  3388.     function cleanPatternName(str) {
  3389.         return str
  3390.             .toLowerCase()
  3391.             .replace(/\.\w+$/, '')
  3392.             .replace(/-\d+x\d+$|-variant$/i, '')
  3393.             .replace(/^\d+[a-z]+-|-.*$/i, '')
  3394.             .replace(/\s+/g, ' ')
  3395.             .trim()
  3396.             .split(' ')
  3397.             .map(word => word.charAt(0).toUpperCase() + word.slice(1))
  3398.             .join(" ");
  3399.     }
  3400.  
  3401.     dom.collectionThumbnails.innerHTML = "";
  3402.     console.log("Cleared existing thumbnails");
  3403.  
  3404.     validPatterns.forEach(pattern => {
  3405.         console.log("Processing pattern:", pattern);
  3406.         pattern.displayName = cleanPatternName(pattern.name);
  3407.         const thumb = document.createElement("div");
  3408.         thumb.className = "thumbnail cursor-pointer border-1 border-transparent";
  3409.         thumb.dataset.patternId = pattern.id || pattern.name.toLowerCase().replace(/\s+/g, '-');
  3410.         thumb.style.width = "120px";
  3411.         thumb.style.boxSizing = "border-box";
  3412.  
  3413.         const img = document.createElement("img");
  3414.         img.src = normalizePath(pattern.thumbnail) || "https://so-animation.com/colorflex/data/collections/fallback.jpg";
  3415.         img.alt = pattern.displayName;
  3416.         img.className = "w-full h-auto";
  3417.         img.onerror = () => {
  3418.             console.warn(`Failed to load thumbnail for ${pattern.displayName}: ${img.src}`);
  3419.             if (img.src !== "https://so-animation.com/colorflex/data/collections/fallback.jpg") {
  3420.                 img.src = "https://so-animation.com/colorflex/data/collections/fallback.jpg";
  3421.                 img.onerror = () => {
  3422.                     console.warn(`Failed to load fallback for ${pattern.displayName}`);
  3423.                     const placeholder = document.createElement("div");
  3424.                     placeholder.textContent = pattern.displayName || "Thumbnail Unavailable";
  3425.                     placeholder.style.width = "100%";
  3426.                     placeholder.style.height = "80px";
  3427.                     placeholder.style.backgroundColor = "#e0e0e0";
  3428.                     placeholder.style.border = "1px solid #ccc";
  3429.                     placeholder.style.display = "flex";
  3430.                     placeholder.style.alignItems = "center";
  3431.                     placeholder.style.justifyContent = "center";
  3432.                     placeholder.style.fontSize = "12px";
  3433.                     placeholder.style.textAlign = "center";
  3434.                     placeholder.style.padding = "5px";
  3435.                     placeholder.style.boxSizing = "border-box";
  3436.                     thumb.replaceChild(placeholder, img);
  3437.                     img.onerror = null;
  3438.                     console.log(`Replaced failed thumbnail for ${pattern.displayName} with placeholder div`);
  3439.                 };
  3440.             } else {
  3441.                 const placeholder = document.createElement("div");
  3442.                 placeholder.textContent = pattern.displayName || "Thumbnail Unavailable";
  3443.                 placeholder.style.width = "100%";
  3444.                 placeholder.style.height = "80px";
  3445.                 placeholder.style.backgroundColor = "#e0e0e0";
  3446.                 placeholder.style.border = "1px solid #ccc";
  3447.                 placeholder.style.display = "flex";
  3448.                 placeholder.style.alignItems = "center";
  3449.                 placeholder.style.justifyContent = "center";
  3450.                 placeholder.style.fontSize = "12px";
  3451.                 placeholder.style.textAlign = "center";
  3452.                 placeholder.style.padding = "5px";
  3453.                 placeholder.style.boxSizing = "border-box";
  3454.                 thumb.replaceChild(placeholder, img);
  3455.                 img.onerror = null;
  3456.                 console.log(`Replaced failed thumbnail for ${pattern.displayName} with placeholder div`);
  3457.             }
  3458.         };
  3459.  
  3460.         thumb.appendChild(img);
  3461.  
  3462.         const label = document.createElement("p");
  3463.         label.textContent = pattern.displayName;
  3464.         label.className = "text-center";
  3465.         thumb.appendChild(label);
  3466.  
  3467.         if (appState.currentPattern && String(appState.currentPattern.id) === String(pattern.id)) {
  3468.             thumb.classList.add("selected");
  3469.             console.log(`Applied 'selected' class to ${pattern.displayName}`);
  3470.         }
  3471.  
  3472.         thumb.addEventListener("click", (e) => {
  3473.             console.log(`Thumbnail clicked: ${pattern.displayName}, ID: ${thumb.dataset.patternId}`);
  3474.             handleThumbnailClick(thumb.dataset.patternId);
  3475.             document.querySelectorAll(".thumbnail").forEach(t => t.classList.remove("selected"));
  3476.             thumb.classList.add("selected");
  3477.         });
  3478.  
  3479.         dom.collectionThumbnails.appendChild(thumb);
  3480.     });
  3481.     console.log("Pattern thumbnails populated:", validPatterns.length);
  3482.  
  3483.     // Update collection header
  3484.     if (dom.collectionHeader) {
  3485.         dom.collectionHeader.textContent = toInitialCaps(appState.selectedCollection?.name || "Unknown");
  3486.         console.log("Updated collectionHeader:", dom.collectionHeader.textContent);
  3487.     }
  3488. }
  3489.  
  3490. // Populate coordinates thumbnails in #coordinatesContainer
  3491. const populateCoordinates = () => {
  3492.     if (!dom.coordinatesContainer) {
  3493.         console.error("coordinatesContainer not found in DOM");
  3494.         return;
  3495.     }
  3496.    
  3497.     dom.coordinatesContainer.innerHTML = "";
  3498.    
  3499.     const coordinates = appState.selectedCollection?.coordinates || [];
  3500.     console.log("Collection coordinates data:", coordinates);
  3501.  
  3502.     if (!coordinates.length) {
  3503.         console.log("No matching coordinates available for collection:", appState.selectedCollection?.name);
  3504.         return;
  3505.     }
  3506.  
  3507.     const numCoordinates = coordinates.length;
  3508.     const xStep = 80;
  3509.     const yStep = 60;
  3510.    
  3511.     // Get actual container dimensions
  3512.     const containerWidth = dom.coordinatesContainer.offsetWidth || 600;
  3513.     const containerHeight = dom.coordinatesContainer.offsetHeight || 300;
  3514.    
  3515.     // Calculate total span and center the layout
  3516.     const totalXSpan = (numCoordinates - 1) * xStep;
  3517.     const totalYSpan = numCoordinates > 1 ? yStep : 0;
  3518.    
  3519.     const xStart = (containerWidth / 2) - (totalXSpan / 2);
  3520.     const yStart = (containerHeight / 2) - (totalYSpan / 2);
  3521.  
  3522.     coordinates.forEach((coord, index) => {
  3523.         const div = document.createElement("div");
  3524.         div.className = "coordinate-item";
  3525.        
  3526.         const xOffset = xStart + (index * xStep);
  3527.         const yOffset = yStart + (index % 2 === 0 ? 0 : yStep);
  3528.        
  3529.         div.style.setProperty("--x-offset", `${xOffset}px`);
  3530.         div.style.setProperty("--y-offset", `${yOffset}px`);
  3531.  
  3532.         const img = document.createElement("img");
  3533.         const normalizedPath = normalizePath(coord.path);
  3534.         console.log(`๐Ÿ” Coordinate path: "${coord.path}" โ†’ normalized: "${normalizedPath}"`);
  3535.         img.src = normalizedPath || "https://so-animation.com/colorflex/data/collections/default-coordinate.jpg";
  3536.         img.alt = coord.pattern || `Coordinate ${index + 1}`;
  3537.         img.className = "coordinate-image";
  3538.         img.dataset.filename = coord.path || "fallback";
  3539.        
  3540.         img.onerror = () => {
  3541.             console.warn(`Failed to load coordinate image: ${img.src}`);
  3542.             const placeholder = document.createElement("div");
  3543.             placeholder.className = "coordinate-placeholder";
  3544.             placeholder.textContent = coord.pattern || "Coordinate Unavailable";
  3545.             div.replaceChild(placeholder, img);
  3546.         };
  3547.  
  3548.         div.appendChild(img);
  3549.         dom.coordinatesContainer.appendChild(div);
  3550.     });
  3551.         console.log("Coordinates populated:", coordinates.length);
  3552.         setupCoordinateImageHandlers();
  3553.  
  3554. };
  3555.  
  3556. // Populate the layer inputs UI
  3557. function populateLayerInputs(pattern = appState.currentPattern) {
  3558.   try {
  3559.     console.log("๐ŸŽ›๏ธ populateLayerInputs called with pattern:", pattern?.name);
  3560.    
  3561.     if (!pattern) {
  3562.       console.error("โŒ No pattern provided or set in appState.");
  3563.       return;
  3564.     }
  3565.  
  3566.     handlePatternSelection(pattern.name);
  3567.     appState.layerInputs = [];
  3568.     appState.currentLayers = [];
  3569.  
  3570.     if (!dom.layerInputsContainer) {
  3571.       console.error("โŒ layerInputsContainer not found in DOM");
  3572.       console.log("๐Ÿ” Available DOM elements:", Object.keys(dom));
  3573.       return;
  3574.     }
  3575.    
  3576.     console.log("โœ… layerInputsContainer found:", dom.layerInputsContainer);
  3577.  
  3578.     const designerColors = pattern.designer_colors || [];
  3579.  
  3580.     // Get all layers (including shadows)
  3581.     const allLayers = buildLayerModel(
  3582.       pattern,
  3583.       designerColors,
  3584.       {
  3585.         isWallPanel: appState.selectedCollection?.name === "wall-panels",
  3586.         tintWhite: appState.tintWhite || false
  3587.       }
  3588.     );
  3589.  
  3590.     // Store all layers in currentLayers
  3591.     appState.currentLayers = allLayers;
  3592.     dom.layerInputsContainer.innerHTML = "";
  3593.  
  3594.     // Create inputs ONLY for non-shadow layers
  3595.     const inputLayers = allLayers.filter(layer => !layer.isShadow);
  3596.    
  3597.     // Add inputs directly to container (no row wrappers)
  3598.     inputLayers.forEach(layer => {
  3599.       const layerData = createColorInput(
  3600.         layer.label,
  3601.         layer.inputId,
  3602.         layer.color,
  3603.         layer.isBackground
  3604.       );
  3605.  
  3606.       appState.layerInputs.push({
  3607.         input: layerData.input,
  3608.         circle: layerData.circle,
  3609.         label: layerData.label,
  3610.         isBackground: layerData.isBackground,
  3611.         color: layer.color,
  3612.         hex: lookupColor(layer.color) || "#FFFFFF"
  3613.       });
  3614.  
  3615.       // Add directly to container - no row grouping needed!
  3616.       dom.layerInputsContainer.appendChild(layerData.container);
  3617.     });
  3618.  
  3619.     console.log("โœ… Populated layerInputs:", appState.layerInputs.map(l => ({
  3620.       label: l.label,
  3621.       value: l.input.value
  3622.     })));
  3623.    
  3624.     console.log("โœ… All layers (including shadows):", appState.currentLayers.map(l => ({
  3625.       label: l.label,
  3626.       isShadow: l.isShadow,
  3627.       path: l.path
  3628.     })));
  3629.    
  3630.     // Add save button after pattern layers are populated
  3631.     addSaveButton();
  3632.    
  3633.     } catch (e) {
  3634.         console.error("โŒ Error in populateLayerInputs:", e);
  3635.     }
  3636. }
  3637.  
  3638. if (USE_GUARD && DEBUG_TRACE) {
  3639.   populateLayerInputs = guard(traceWrapper(populateLayerInputs, "populateLayerInputs"));
  3640. } else if (USE_GUARD) {
  3641.   populateLayerInputs = guard(populateLayerInputs, "populateLayerInputs");
  3642. }
  3643.  
  3644. if (USE_GUARD && DEBUG_TRACE) {
  3645.   populateLayerInputs = guard(traceWrapper(populateLayerInputs, "populateLayerInputs"));
  3646. } else if (USE_GUARD) {
  3647.   populateLayerInputs = guard(populateLayerInputs, "populateLayerInputs");
  3648. }
  3649.  
  3650.  
  3651. function handlePatternSelection(patternName, preserveColors = false) {
  3652.     console.log(`handlePatternSelection: pattern=${patternName}, lockedCollection=${appState.lockedCollection}, currentCollection=${appState.selectedCollection?.name}`);
  3653.     const pattern = appState.selectedCollection.patterns.find(
  3654.         p => p.name.toUpperCase() === patternName.toUpperCase()
  3655.     ) || appState.selectedCollection.patterns[0];
  3656.     if (!pattern) {
  3657.         console.error(`Pattern ${patternName} not found in selected collection`);
  3658.         return;
  3659.     }
  3660.     appState.currentPattern = pattern;
  3661.     console.log("Pattern set to:", appState.currentPattern.name);
  3662.     console.log("Layer labels available:", appState.currentPattern.layerLabels);
  3663.     console.log("Layers available:", JSON.stringify(appState.currentPattern.layers, null, 2));
  3664.  
  3665.     const designerColors = appState.currentPattern.designer_colors || [];
  3666.     const curatedColors = appState.selectedCollection.curatedColors || [];
  3667.     const colorSource = designerColors.length > 0 ? designerColors : curatedColors;
  3668.     console.log("Color source:", JSON.stringify(colorSource, null, 2));
  3669.  
  3670.     // Save current color values if preserving
  3671.     const savedColors = preserveColors ?
  3672.         appState.currentLayers.map(layer => layer.color) : [];
  3673.  
  3674.     appState.currentLayers = [];
  3675.     let colorIndex = 0; // โœ… Make sure this is only declared once
  3676.  
  3677.     const patternType = getPatternType(pattern, appState.selectedCollection);
  3678.     console.log(`๐Ÿ” Pattern type detected: ${patternType} for pattern: ${pattern.name} in collection: ${appState.selectedCollection?.name}`);
  3679.     const isWallPanel = patternType === "wall-panel";
  3680.     const isWall = pattern.isWall || isWallPanel;
  3681.  
  3682.     if (isWall) {
  3683.         const wallColor = colorSource[colorIndex] || "#FFFFFF";
  3684.         appState.currentLayers.push({
  3685.             imageUrl: null,
  3686.             color: wallColor,
  3687.             label: "Wall Color",
  3688.             isShadow: false
  3689.         });
  3690.         colorIndex++;
  3691.     }
  3692.  
  3693.     const backgroundColor = colorSource[colorIndex] || "#FFFFFF";
  3694.     appState.currentLayers.push({
  3695.         imageUrl: null,
  3696.         color: backgroundColor,
  3697.         label: "Background",
  3698.         isShadow: false
  3699.     });
  3700.     colorIndex++;
  3701.  
  3702.     if (!appState.currentPattern.tintWhite) {
  3703.         const overlayLayers = pattern.layers || [];
  3704.         console.log(`Processing ${overlayLayers.length} overlay layers`);
  3705.         overlayLayers.forEach((layer, index) => {
  3706.             const layerPath = layer.path || "";
  3707.             const label = pattern.layerLabels[index] || `Layer ${index + 1}`;
  3708.             const isShadow = layer.isShadow === true;
  3709.             if (!isShadow) {
  3710.                 const layerColor = colorSource[colorIndex] || "#000000";
  3711.                 appState.currentLayers.push({
  3712.                     imageUrl: layerPath,
  3713.                     color: layerColor,
  3714.                     label: label,
  3715.                     isShadow: false
  3716.                 });
  3717.                 console.log(`Assigned color to ${label}: ${layerColor}`);
  3718.                 colorIndex++;
  3719.             }
  3720.         });
  3721.         console.log("Final appState.currentLayers:", JSON.stringify(appState.currentLayers, null, 2));
  3722.     }
  3723.  
  3724.     // Restore saved colors if preserving
  3725.     if (preserveColors && savedColors.length > 0) {
  3726.         appState.currentLayers.forEach((layer, index) => {
  3727.             if (savedColors[index] && layer.color) {
  3728.                 layer.color = savedColors[index];
  3729.             }
  3730.         });
  3731.         console.log("๐Ÿ”„ Colors preserved from previous selection");
  3732.     }
  3733. }
  3734.  
  3735. function applyColorsToLayerInputs(colors, curatedColors = []) {
  3736.     console.log("Applying colors to layer inputs:", colors,
  3737.                 "Curated colors:", curatedColors,
  3738.                 "Layer inputs length:", appState.layerInputs.length,
  3739.                 "Current layers length:", appState.currentLayers.length);
  3740.     appState.layerInputs.forEach((layer, index) => {
  3741.         if (index >= appState.currentLayers.length) {
  3742.             console.warn(`Skipping input ${layer.label} at index ${index}: no corresponding currentLayer`);
  3743.             return;
  3744.         }
  3745.         const color = colors[index] || curatedColors[index] || (layer.isBackground ? "#FFFFFF" : "Snowbound");
  3746.         const cleanColor = color.replace(/^(SW|SC)\d+\s*/i, "").trim();
  3747.         const hex = lookupColor(color) || "#FFFFFF";
  3748.         layer.input.value = toInitialCaps(cleanColor);
  3749.         layer.circle.style.backgroundColor = hex;
  3750.         console.log(`Applied ${cleanColor} (${hex}) to ${layer.label} input (index ${index})`);
  3751.        
  3752.         appState.currentLayers[index].color = cleanColor;
  3753.     });
  3754.     console.log("Inputs after apply:",
  3755.                 appState.layerInputs.map(l => ({ id: l.input.id, label: l.label, value: l.input.value })));
  3756.     updateDisplays();
  3757. }
  3758.  
  3759. // Highlight active layer
  3760. const highlightActiveLayer = (circle) => {
  3761.     document.querySelectorAll(".circle-input").forEach((c) => (c.style.outline = "none"));
  3762.     circle.style.outline = "6px solid rgb(244, 255, 219)";
  3763. };
  3764.  
  3765.  
  3766. // Fixed processImage function with corrected normalization logic
  3767. let processImage = (url, callback, layerColor = '#7f817e', gamma = 2.2, isShadow = false, isWallPanel = false, isWall = false) => {
  3768.     console.log("๐Ÿ” processImage called from:", new Error().stack.split('\n')[2]);
  3769.    
  3770.     // Normalize the URL path to fix ./data/ vs data/ inconsistencies
  3771.     const normalizedUrl = normalizePath(url);
  3772.     console.log(`Processing image ${url} -> ${normalizedUrl} with color ${layerColor}, Normalization: ${USE_NORMALIZATION}, IsShadow: ${isShadow}, IsWallPanel: ${isWallPanel}, IsWall: ${isWall}`);
  3773.    
  3774.     const img = new Image();
  3775.     img.crossOrigin = "Anonymous";
  3776.     img.src = `${normalizedUrl}?t=${new Date().getTime()}`;
  3777.  
  3778.     img.onload = () => {
  3779.         console.log(`โœ… Processed image: ${img.src} (${img.naturalWidth}x${img.naturalHeight})`);
  3780.         console.log("Image loaded successfully:", url);
  3781.  
  3782.         const canvas = document.createElement("canvas");
  3783.         const ctx = canvas.getContext("2d");
  3784.  
  3785.         const width = img.width;
  3786.         const height = img.height;
  3787.         canvas.width = width;
  3788.         canvas.height = height;
  3789.  
  3790.         if (isWall && (!url || url === "")) {
  3791.             ctx.fillStyle = layerColor;
  3792.             ctx.fillRect(0, 0, width, height);
  3793.             console.log("Applied solid wall color:", layerColor);
  3794.             callback(canvas);
  3795.             return;
  3796.         }
  3797.  
  3798.         ctx.drawImage(img, 0, 0, width, height);
  3799.         let imageData;
  3800.         try {
  3801.             imageData = ctx.getImageData(0, 0, width, height);
  3802.         } catch (e) {
  3803.             console.warn("โš ๏ธ Canvas tainted, returning image without processing:", e.message);
  3804.             callback(canvas);
  3805.             return;
  3806.         }
  3807.         const data = imageData.data;
  3808.  
  3809.         console.log("Original Sample (R,G,B,A):", data[0], data[1], data[2], data[3]);
  3810.  
  3811.         let rLayer, gLayer, bLayer;
  3812.         if (layerColor && !isShadow) {
  3813.             const hex = layerColor.replace("#", "");
  3814.             rLayer = parseInt(hex.substring(0, 2), 16);
  3815.             gLayer = parseInt(hex.substring(2, 4), 16);
  3816.             bLayer = parseInt(hex.substring(4, 6), 16);
  3817.             console.log(`Layer color parsed: R=${rLayer}, G=${gLayer}, B=${bLayer}`);
  3818.         } else if (isShadow) {
  3819.             console.log("Shadow layer: Skipping color parsing");
  3820.         }
  3821.  
  3822.         if (isWallPanel && layerColor && !isShadow) {
  3823.             // Wall panel processing
  3824.             const isDesignLayer = url.toLowerCase().includes("design");
  3825.             const isBackLayer = url.toLowerCase().includes("back");
  3826.             const layerType = isDesignLayer ? "Design" : isBackLayer ? "Back" : "Other";
  3827.             let designPixelCount = 0;
  3828.             let transparentPixelCount = 0;
  3829.  
  3830.             console.log(`๐Ÿ” Wall panel debug - Layer type: ${layerType}`);
  3831.             console.log(`๐Ÿ” Data array length: ${data.length}`);
  3832.             console.log(`๐Ÿ” Image dimensions: ${canvas.width}x${canvas.height}`);
  3833.             console.log(`๐Ÿ” Expected pixels: ${canvas.width * canvas.height}`);
  3834.             console.log(`๐Ÿ” First 3 pixels:`,
  3835.                 `(${data[0]},${data[1]},${data[2]},${data[3]})`,
  3836.                 `(${data[4]},${data[5]},${data[6]},${data[7]})`,
  3837.                 `(${data[8]},${data[9]},${data[10]},${data[11]})`);
  3838.  
  3839.             applyNormalizationProcessing(data, rLayer, gLayer, bLayer);
  3840.            
  3841.             console.log(`Processed ${layerType} layer: Design pixels=${designPixelCount}, Transparent pixels=${transparentPixelCount}`);
  3842.         } else if (isShadow) {
  3843.             // Shadow processing
  3844.                 console.log("๐Ÿ” Processing shadow layer");
  3845.  
  3846.             for (let i = 0; i < data.length; i += 4) {
  3847.                 const luminance = 0.299 * data[i] + 0.587 * data[i + 1] + 0.114 * data[i + 2];
  3848.                 const alpha = 1 - (luminance / 255);
  3849.                 data[i] = 0;
  3850.                 data[i + 1] = 0;
  3851.                 data[i + 2] = 0;
  3852.                 data[i + 3] = Math.round(alpha * 255);
  3853.             }
  3854.         } else if (layerColor && USE_NORMALIZATION) {
  3855.             // Standard pattern normalization
  3856.             applyNormalizationProcessing(data, rLayer, gLayer, bLayer);
  3857.         } else if (layerColor) {
  3858.             // Standard brightness-based masking (when normalization is off)
  3859.             let recoloredPixels = 0;
  3860.             let maskedPixels = 0;
  3861.  
  3862.             for (let i = 0; i < data.length; i += 4) {
  3863.                 const r = data[i];
  3864.                 const g = data[i + 1];
  3865.                 const b = data[i + 2];
  3866.                 const a = data[i + 3];
  3867.  
  3868.                 const brightness = (r + g + b) / 3;
  3869.  
  3870.                 if (brightness < 200 && a > 0) {
  3871.                     data[i] = rLayer;
  3872.                     data[i + 1] = gLayer;
  3873.                     data[i + 2] = bLayer;
  3874.                     data[i + 3] = 255;
  3875.                     recoloredPixels++;
  3876.                 } else {
  3877.                     data[i + 3] = 0;
  3878.                     maskedPixels++;
  3879.                 }
  3880.             }
  3881.  
  3882.             console.log(`Recolored pixels: ${recoloredPixels}, Transparent (masked): ${maskedPixels}`);
  3883.         }
  3884.  
  3885.         console.log("Processed Sample (R,G,B,A):", data[0], data[1], data[2], data[3]);
  3886.         ctx.putImageData(imageData, 0, 0);
  3887.         callback(canvas);
  3888.     };
  3889.  
  3890.     img.onerror = () => console.error(`Canvas image load failed: ${url}`);
  3891. };
  3892.    // GUARD / TRACE WRAPPER
  3893.     if (USE_GUARD && DEBUG_TRACE) {
  3894.     processImage = guard(traceWrapper(processImage, "processImage")); // Wrapped for debugging
  3895.     } else if (USE_GUARD) {
  3896.         processImage = guard(processImage, "processImage"); // Wrapped for debugging
  3897.     }
  3898.  
  3899.  
  3900.     // Load pattern data from JSON
  3901. function loadPatternData(collection, patternId) {
  3902.     console.log(`loadPatternData: patternId=${patternId}`);
  3903.    
  3904.     const pattern = collection.patterns.find(p => p.id === patternId);
  3905.        
  3906.     if (pattern) {
  3907.         console.log(`โœ… Found pattern "${pattern.name}" (ID: ${pattern.id}) in collection "${collection.name}"`);
  3908.         appState.currentPattern = pattern;
  3909.  
  3910.         // ===== INSERT DEBUG LOGS HERE =====
  3911.         console.log("๐Ÿ” SOURCE DATA DEBUG:");
  3912.         console.log("  Current pattern:", appState.currentPattern?.name);
  3913.         console.log("  Designer colors:", appState.currentPattern?.designer_colors);
  3914.         console.log("  Layer labels:", appState.currentPattern?.layerLabels);
  3915.         console.log("  Layers array:", appState.currentPattern?.layers?.map((l, i) => `${i}: ${l.path?.split('/').pop()}`));
  3916.  
  3917.         // Check if this is a furniture collection
  3918.         const isFurnitureCollection = appState.selectedCollection?.wallMask != null ||
  3919.                                         appState.selectedCollection?.furnitureType != null;
  3920.        
  3921.         if (isFurnitureCollection) {
  3922.             appState.furnitureMode = true;
  3923.         }
  3924.  
  3925.         // โœ… Build layer + input models once pattern is set
  3926.         populateLayerInputs(pattern);
  3927.        
  3928.  
  3929.         // ===== DEBUG AFTER populateLayerInputs =====
  3930.         console.log("๐ŸŽ›๏ธ UI POPULATION DEBUG:");
  3931.         console.log("  currentLayers count:", appState.currentLayers?.length);
  3932.         console.log("  currentLayers content:");
  3933.         appState.currentLayers?.forEach((layer, index) => {
  3934.             console.log(`    ${index}: "${layer.label}" = "${layer.color}"`);
  3935.         });
  3936.  
  3937.         // ===== DEBUG ACTUAL DOM INPUTS =====
  3938.         setTimeout(() => {
  3939.             console.log("๐Ÿ” ACTUAL UI INPUTS:");
  3940.             const inputs = document.querySelectorAll('.layer-input');
  3941.             inputs.forEach((input, index) => {
  3942.                 const container = input.closest('.layer-input-container');
  3943.                 const label = container?.querySelector('.layer-label')?.textContent;
  3944.                 console.log(`    UI Input ${index}: "${label}" = "${input.value}"`);
  3945.             });
  3946.         }, 100); // Small delay to ensure DOM is updated
  3947.  
  3948.         console.log(">>> Updated appState.currentPattern:", JSON.stringify(pattern, null, 2));
  3949.         appState.curatedColors = appState.selectedCollection.curatedColors || [];
  3950.         console.log(">>> Updated appState.curatedColors:", appState.curatedColors);
  3951.        
  3952.         if (!Array.isArray(appState.colorsData) || appState.colorsData.length === 0) {
  3953.             console.warn("๐Ÿ›‘ Sherwin-Williams colors not loaded yet. Delaying populateCuratedColors.");
  3954.             return;
  3955.         }
  3956.  
  3957.         // โœ… Only call curated color population when everything is ready
  3958.         if (appState.colorsData.length && collection.curatedColors?.length) {
  3959.             appState.curatedColors = collection.curatedColors;
  3960.             populateCuratedColors(appState.curatedColors);
  3961.         } else {
  3962.             console.warn("X Not populating curated colors - missing data");
  3963.         }
  3964.  
  3965.         const isFurniturePattern = appState.currentPattern?.isFurniture || false;
  3966.        
  3967.         updatePreview();
  3968.        
  3969.         // Check if we're in fabric mode - if so, only render fabric mockup
  3970.         if (appState.isInFabricMode) {
  3971.             console.log("๐Ÿงต loadPatternData in fabric mode - calling renderFabricMockup()");
  3972.             renderFabricMockup();
  3973.         } else {
  3974.             updateRoomMockup();
  3975.         }
  3976.        
  3977.         populatePatternThumbnails(appState.selectedCollection.patterns);
  3978.         populateCoordinates();
  3979.  
  3980.     } else {
  3981.         console.error(">>> Pattern not found:", patternId);
  3982.     }
  3983. }
  3984.     // GUARD / TRACE WRAPPER
  3985.     if (USE_GUARD && DEBUG_TRACE) {
  3986.     loadPatternData = guard(traceWrapper(loadPatternData, "loadPatternData")); // Wrapped for debugging
  3987.     } else if (USE_GUARD) {
  3988.         loadPatternData = guard(loadPatternData, "loadPatternData"); // Wrapped for debugging
  3989.     }
  3990.  
  3991.     // Pattern scaling
  3992.     window.setPatternScale = function(multiplier) {
  3993.         appState.scaleMultiplier = multiplier;
  3994.         console.log(`>>> Scale multiplier set to: ${appState.scaleMultiplier}`);
  3995.  
  3996.         // Highlight active button
  3997.         document.querySelectorAll('#scaleControls button').forEach(btn => {
  3998.             const btnMultiplier = parseFloat(btn.dataset.multiplier);
  3999.             if (btnMultiplier === multiplier) {
  4000.                 btn.classList.add('!bg-blue-500', 'text-white', 'active-scale');
  4001.                 btn.classList.remove('!bg-gray-200');
  4002.             } else {
  4003.                 btn.classList.add('!bg-gray-200');
  4004.                 btn.classList.remove('!bg-blue-500', 'text-white', 'active-scale');
  4005.             }
  4006.         });
  4007.  
  4008.         // Check if we're in fabric mode - if so, only render fabric mockup
  4009.         if (appState.isInFabricMode) {
  4010.             console.log("๐Ÿงต setPatternScale in fabric mode - calling renderFabricMockup()");
  4011.             renderFabricMockup();
  4012.         } else {
  4013.             updateRoomMockup();
  4014.         }
  4015.  
  4016.         const isFurniturePattern = appState.currentPattern?.isFurniture || false;
  4017.  
  4018.         updatePreview();
  4019.        
  4020.     };
  4021.     // GUARD / TRACE WRAPPER
  4022.     if (USE_GUARD && DEBUG_TRACE) {
  4023.     setPatternScale = guard(traceWrapper(setPatternScale, "setPatternScale")); // Wrapped for debugging
  4024.     } else if (USE_GUARD) {
  4025.         setPatternScale = guard(setPatternScale, "setPatternScale"); // Wrapped for debugging
  4026.     }
  4027.    
  4028.     // Initialize scale on page load
  4029.     document.addEventListener('DOMContentLoaded', () => {
  4030.         appState.scaleMultiplier = 1; // Default to Normal
  4031.         setPatternScale(1);
  4032.         console.log('setPatternScale called with multiplier:', appState.scaleMultiplier);
  4033.     });
  4034.  
  4035.  
  4036.     // Ensure updatePreview is defined before updateDisplays uses it
  4037. // ============================================================================
  4038. // CORE ISSUES AND FIXES
  4039. // ============================================================================
  4040.  
  4041. // 1. Fix buildLayerModel to return a flat array consistently
  4042. function buildLayerModel(pattern, designerColors = [], options = {}) {
  4043.     const { isWallPanel = false, tintWhite = false } = options;
  4044.     const patternLayers = pattern.layers || [];
  4045.     const layerLabels = pattern.layerLabels || [];
  4046.  
  4047.     console.log("๐Ÿ—๏ธ buildLayerModel LABEL FIX DEBUG:");
  4048.     console.log("  Pattern layers:", patternLayers.length);
  4049.     console.log("  Layer labels:", layerLabels);
  4050.     console.log("  Designer colors available:", designerColors.length);
  4051.  
  4052.     let colorIndex = 0;
  4053.     let inputIndex = 0;
  4054.     const allLayers = [];
  4055.  
  4056.     // Check if this is a furniture collection
  4057. console.log("๐Ÿ” FURNITURE DETECTION DEBUG:");
  4058. console.log("  selectedCollection name:", appState.selectedCollection?.name);
  4059. console.log("  selectedCollection wallMask:", appState.selectedCollection?.wallMask);
  4060. console.log("  selectedCollection furnitureType:", appState.selectedCollection?.furnitureType);
  4061. console.log("  selectedCollection keys:", Object.keys(appState.selectedCollection || {}));
  4062.  
  4063. const isFurnitureCollection = appState.selectedCollection?.wallMask != null;
  4064. console.log("  isFurnitureCollection result:", isFurnitureCollection);
  4065.    
  4066.  
  4067.     if (isFurnitureCollection) {
  4068.         // Add wall color layer
  4069.         const furnitureConfig = appState.selectedCollection?.furnitureConfig;
  4070.         const defaultWallColor = furnitureConfig?.defaultWallColor || "SW7006 Extra White";
  4071.        
  4072.         allLayers.push({
  4073.             label: "Wall Color",
  4074.             color: defaultWallColor,
  4075.             path: null,
  4076.             isBackground: false,
  4077.             isShadow: false,
  4078.             isWallPanel: false,
  4079.             inputId: `layer-${inputIndex++}`
  4080.         });
  4081.         console.log(`  โœ… Added Wall Color (default): ${defaultWallColor}`);
  4082.  
  4083.         // Add sofa base layer  
  4084.         allLayers.push({
  4085.             label: "BG/Sofa Base",
  4086.             color: designerColors[colorIndex++] || "Snowbound",
  4087.             path: null,
  4088.             isBackground: true,
  4089.             isShadow: false,
  4090.             isWallPanel: false,
  4091.             inputId: `layer-${inputIndex++}`
  4092.         });
  4093.         console.log(`  โœ… Added BG/Sofa Base (designer color ${colorIndex - 1})`);
  4094.  
  4095.     } else {
  4096.         // Standard collection - just background
  4097.         allLayers.push({
  4098.             label: "Background",
  4099.             color: designerColors[colorIndex++] || "Snowbound",
  4100.             path: null,
  4101.             isBackground: true,
  4102.             isShadow: false,
  4103.             isWallPanel: false,
  4104.             inputId: `layer-${inputIndex++}`
  4105.         });
  4106.     }
  4107.  
  4108.     // โœ… PATTERN LAYERS (shared by both furniture and standard)
  4109.     console.log("  ๐ŸŽจ Processing pattern layers:");
  4110.     let patternLabelIndex = 0;
  4111.  
  4112.     for (let i = 0; i < patternLayers.length; i++) {
  4113.         const layer = patternLayers[i];
  4114.         const isTrueShadow = layer.isShadow === true;
  4115.  
  4116.         if (!isTrueShadow) {
  4117.             const originalLabel = layerLabels[patternLabelIndex] || `Pattern Layer ${patternLabelIndex + 1}`;
  4118.            
  4119.             const layerObj = {
  4120.                 label: originalLabel,
  4121.                 color: designerColors[colorIndex++] || "Snowbound",
  4122.                 path: layer.path || "",
  4123.                 isBackground: false,
  4124.                 isShadow: false,
  4125.                 isWallPanel: false,
  4126.                 tintWhite,
  4127.                 inputId: `layer-${inputIndex++}`,
  4128.                 patternLayerIndex: i
  4129.             };
  4130.  
  4131.             allLayers.push(layerObj);
  4132.             console.log(`    โœ… Added pattern layer: "${originalLabel}" (designer color ${colorIndex - 1})`);
  4133.             patternLabelIndex++;
  4134.        
  4135.     } else {
  4136.             // Shadow layers (no input needed)
  4137.             const layerObj = {
  4138.                 label: `Shadow ${i + 1}`,
  4139.                 color: null,
  4140.                 path: layer.path || "",
  4141.                 isBackground: false,
  4142.                 isShadow: true,
  4143.                 isWallPanel: false,
  4144.                 tintWhite,
  4145.                 inputId: null,
  4146.                 patternLayerIndex: i
  4147.             };
  4148.  
  4149.             allLayers.push(layerObj);
  4150.             console.log(`    โœ… Added shadow layer: "Shadow ${i + 1}" (no color index used)`);
  4151.         }
  4152.     }
  4153.  
  4154.     console.log(`๐Ÿ—๏ธ Final layer model (used ${colorIndex} designer colors):`);
  4155.     allLayers.forEach((layer, index) => {
  4156.         const type = layer.isBackground ? 'bg' : layer.isShadow ? 'shadow' : 'layer';
  4157.         console.log(`  ${index}: ${layer.label} (${type}) = ${layer.color || 'no color'}`);
  4158.     });
  4159.  
  4160.     // VALIDATION: Check counts
  4161.     const inputLayers = allLayers.filter(l => !l.isShadow);
  4162.     console.log(`โœ… Created ${inputLayers.length} input layers, used ${colorIndex} designer colors`);
  4163.    
  4164.     if (designerColors.length < colorIndex) {
  4165.         console.warn(`โš ๏ธ Not enough designer colors: need ${colorIndex}, have ${designerColors.length}`);
  4166.     }
  4167.  
  4168.     // Add this at the very end of buildLayerModel(), just before the return statement
  4169. console.log(`๐Ÿ—๏ธ FINAL LAYER MODEL DEBUG:`);
  4170. console.log(`  Total layers created: ${allLayers.length}`);
  4171. console.log(`  isFurnitureCollection was: ${isFurnitureCollection}`);
  4172. console.log(`  Used ${colorIndex} designer colors`);
  4173. console.log(`  Final layer structure:`);
  4174. allLayers.forEach((layer, index) => {
  4175.     const type = layer.isBackground ? 'bg' : layer.isShadow ? 'shadow' : 'input';
  4176.     console.log(`    ${index}: "${layer.label}" (${type}) = "${layer.color}" | inputId: ${layer.inputId}`);
  4177. });
  4178.  
  4179.  
  4180.     return allLayers;
  4181. }
  4182.  
  4183.  
  4184.  
  4185.  
  4186.  
  4187. // โœ… Wrap in an IIFE to avoid illegal top-level return
  4188. if (appState.currentPattern) {
  4189. (() => {
  4190.   try {
  4191.     const pattern = appState.currentPattern;
  4192.  
  4193.     if (!pattern || !Array.isArray(pattern.layers)) {
  4194.       console.error("โŒ Invalid pattern or missing layers:", pattern);
  4195.       return;
  4196.     }
  4197.  
  4198.     const designerColors = pattern.designer_colors || [];
  4199.  
  4200.     appState.currentLayers = buildLayerModel(
  4201.       pattern,
  4202.       designerColors,
  4203.       {
  4204.         isWallPanel: appState.selectedCollection?.name === "wall-panels",
  4205.         tintWhite: appState.tintWhite || false
  4206.       }
  4207.     );
  4208.  
  4209.     appState.layerInputs = appState.currentLayers.map(layer => {
  4210.       const layerData = createColorInput(
  4211.         layer.label,
  4212.         layer.inputId,
  4213.         layer.color,
  4214.         layer.isBackground
  4215.       );
  4216.       return {
  4217.         ...layerData,
  4218.         color: layer.color,
  4219.         hex: lookupColor(layer.color) || "#FFFFFF"
  4220.       };
  4221.     });
  4222.  
  4223.   } catch (e) {
  4224.     console.error("โŒ Error populating layer inputs:", e);
  4225.   }
  4226. })();
  4227. }
  4228.  
  4229.  
  4230. // 2. updatePreview
  4231. let updatePreview = async () => {
  4232.  
  4233.             console.log("๐Ÿ” updatePreview PATTERN DEBUG:");
  4234.         console.log("  currentPattern name:", appState.currentPattern?.name);
  4235.         console.log("  currentPattern layers:", appState.currentPattern?.layers?.map(l => l.path?.split('/').pop()));
  4236.         console.log("  isFurnitureMode:", appState.furnitureMode);
  4237.         console.log("  selectedCollection name:", appState.selectedCollection?.name);
  4238.        
  4239.         if (!dom.preview) return console.error("preview not found in DOM");
  4240.  
  4241.  
  4242.     try {
  4243.         if (!dom.preview) return console.error("preview not found in DOM");
  4244.         if (!appState.currentPattern) return console.error("No current pattern selected");
  4245.  
  4246.         console.log("๐Ÿ” updatePreview START");
  4247.  
  4248.         // Get responsive canvas size from CSS custom properties
  4249.         const computedStyle = getComputedStyle(document.documentElement);
  4250.         const canvasSize = parseInt(computedStyle.getPropertyValue('--preview-size').replace('px', '')) || 700;
  4251.         console.log("๐Ÿ“ฑ Canvas size from CSS:", canvasSize);
  4252.  
  4253.         const previewCanvas = document.createElement("canvas");
  4254.         const previewCtx = previewCanvas.getContext("2d", { willReadFrequently: true });
  4255.         previewCanvas.width = canvasSize;
  4256.         previewCanvas.height = canvasSize;
  4257.  
  4258.         // Check if this is a furniture collection
  4259.         const isFurnitureCollection = appState.selectedCollection?.wallMask != null;
  4260.         const layerMapping = getLayerMappingForPreview(isFurnitureCollection);
  4261.         console.log("๐Ÿ” SOFA BASE DEBUG:");
  4262.         console.log("  Layer mapping:", layerMapping);
  4263.         console.log("  backgroundIndex:", layerMapping.backgroundIndex);
  4264.         console.log("  Current layers length:", appState.currentLayers.length);
  4265.  
  4266.        
  4267.  
  4268.  
  4269.        
  4270.         console.log("๐Ÿ” Layer mapping:", layerMapping);
  4271.         console.log("๐Ÿ” Current layers:", appState.currentLayers.map((l, i) => `${i}: ${l.label} = ${l.color}`));
  4272.  
  4273.         let patternToRender = appState.currentPattern;
  4274.         let usesBotanicalLayers = false;
  4275.  
  4276.         // For furniture collections, try to find the botanical pattern
  4277.         if (isFurnitureCollection) {
  4278.             console.log("๐ŸŒฟ Furniture mode detected - looking for original pattern");
  4279.            
  4280.             // Try multiple ways to get the original pattern
  4281.             let originalPattern = null;
  4282.            
  4283.             // Method 1: Check if furniture pattern stores original
  4284.             if (appState.currentPattern.originalPattern) {
  4285.                 originalPattern = appState.currentPattern.originalPattern;
  4286.                 console.log("โœ… Found original pattern via .originalPattern");
  4287.             }
  4288.            
  4289.             // Method 2: Look up by name in botanicals collection
  4290.             if (!originalPattern) {
  4291.                 const botanicalCollection = appState.collections.find(c => c.name === "botanicals");
  4292.                 if (botanicalCollection) {
  4293.                     // Remove any furniture prefixes from the name to find botanical pattern
  4294.                     const cleanPatternName = appState.currentPattern.name
  4295.                         .replace(/^.*\s+/, '') // Remove collection prefix
  4296.                         .replace(/\s+\w+\s+sofa$/i, ''); // Remove furniture suffix
  4297.                    
  4298.                     originalPattern = botanicalCollection.patterns.find(p =>
  4299.                         p.name.toLowerCase() === cleanPatternName.toLowerCase() ||
  4300.                         p.name.toLowerCase() === appState.currentPattern.name.toLowerCase()
  4301.                     );
  4302.                    
  4303.                     if (originalPattern) {
  4304.                         console.log("โœ… Found original pattern by name lookup:", originalPattern.name);
  4305.                     }
  4306.                 }
  4307.             }
  4308.            
  4309.             // Method 3: Use stored original collection
  4310.             if (!originalPattern && appState.originalCollection) {
  4311.                 originalPattern = appState.originalCollection.patterns?.find(p =>
  4312.                     p.id === appState.currentPattern.id
  4313.                 );
  4314.                
  4315.                 if (originalPattern) {
  4316.                     console.log("โœ… Found original pattern via originalCollection");
  4317.                 }
  4318.             }
  4319.            
  4320.             if (originalPattern) {
  4321.                 console.log("๐ŸŒฟ Using original pattern for preview:", originalPattern.name);
  4322.                 console.log("  Original layers:", originalPattern.layers?.map(l => l.path.split('/').pop()));
  4323.                
  4324.                 patternToRender = originalPattern;
  4325.                 usesBotanicalLayers = true;
  4326.             } else {
  4327.                 console.warn("โš ๏ธ Could not find original pattern, using furniture pattern");
  4328.             }
  4329.         }
  4330.  
  4331.  
  4332.         // Get background color based on collection type
  4333.         let backgroundLayerIndex = layerMapping.backgroundIndex;
  4334.         let backgroundColor;
  4335.  
  4336.         if (isFurnitureCollection && usesBotanicalLayers) {
  4337.             // โœ… FIX: For furniture mode pattern preview, use the BG/Sofa Base color (index 1)
  4338.             // but this should be the same as the original background color
  4339.             backgroundColor = lookupColor(appState.currentLayers[1]?.color || "Snowbound");
  4340.             console.log(`๐ŸŒฟ Furniture mode pattern preview - using BG/Sofa Base color from input 1: ${backgroundColor}`);
  4341.         } else {
  4342.             // Standard mode or furniture room mockup
  4343.             const backgroundLayer = appState.currentLayers[backgroundLayerIndex];
  4344.             backgroundColor = lookupColor(backgroundLayer?.color || "Snowbound");
  4345.             console.log(`๐ŸŽจ Standard background color from input ${backgroundLayerIndex}: ${backgroundColor}`);
  4346.         }        
  4347.         console.log(`๐ŸŽจ Background color from input ${backgroundLayerIndex}: ${backgroundColor}`);
  4348.  
  4349.         // Clear canvas to transparent
  4350.         previewCtx.clearRect(0, 0, previewCanvas.width, previewCanvas.height);
  4351.  
  4352.         // Handle tint white patterns
  4353.         if (patternToRender.tintWhite && patternToRender.baseComposite) {
  4354.             console.log("๐ŸŽจ Rendering tint white pattern");
  4355.            
  4356.             const baseImage = new Image();
  4357.             baseImage.crossOrigin = "Anonymous";
  4358.             baseImage.src = normalizePath(patternToRender.baseComposite);
  4359.            
  4360.             await new Promise((resolve, reject) => {
  4361.                 baseImage.onload = () => {
  4362.                     const scaleMultiplier = appState.scaleMultiplier || 1;
  4363.                     const imgAspect = baseImage.width / baseImage.height;
  4364.                     const maxSize = canvasSize * scaleMultiplier;
  4365.                    
  4366.                     let drawWidth, drawHeight, offsetX, offsetY;
  4367.                     if (imgAspect > 1) {
  4368.                         drawWidth = Math.min(maxSize, canvasSize);
  4369.                         drawHeight = drawWidth / imgAspect;
  4370.                     } else {
  4371.                         drawHeight = Math.min(maxSize, canvasSize);
  4372.                         drawWidth = drawHeight * imgAspect;
  4373.                     }
  4374.                    
  4375.                     offsetX = (canvasSize - drawWidth) / 2;
  4376.                     offsetY = (canvasSize - drawHeight) / 2;
  4377.                    
  4378.                     previewCtx.fillStyle = backgroundColor;
  4379.                     previewCtx.fillRect(offsetX, offsetY, drawWidth, drawHeight);
  4380.                     previewCtx.drawImage(baseImage, offsetX, offsetY, drawWidth, drawHeight);
  4381.                    
  4382.                     // Apply tint to white areas
  4383.                     let imageData;
  4384.                     try {
  4385.                         imageData = previewCtx.getImageData(offsetX, offsetY, drawWidth, drawHeight);
  4386.                     } catch (e) {
  4387.                         console.warn("โš ๏ธ Canvas tainted, skipping preview tinting:", e.message);
  4388.                         resolve();
  4389.                         return;
  4390.                     }
  4391.                     const data = imageData.data;
  4392.                     const wallColor = lookupColor(appState.currentLayers[0]?.color || "Snowbound");
  4393.                     const hex = wallColor.replace("#", "");
  4394.                     const rTint = parseInt(hex.substring(0, 2), 16);
  4395.                     const gTint = parseInt(hex.substring(2, 4), 16);
  4396.                     const bTint = parseInt(hex.substring(4, 6), 16);
  4397.                    
  4398.                     for (let i = 0; i < data.length; i += 4) {
  4399.                         const r = data[i], g = data[i + 1], b = data[i + 2];
  4400.                         if (r > 240 && g > 240 && b > 240) {
  4401.                             data[i] = rTint;
  4402.                             data[i + 1] = gTint;
  4403.                             data[i + 2] = bTint;
  4404.                         }
  4405.                     }
  4406.                    
  4407.                     previewCtx.putImageData(imageData, offsetX, offsetY);
  4408.                     resolve();
  4409.                 };
  4410.                 baseImage.onerror = reject;
  4411.             });
  4412.            
  4413.         } else if (patternToRender.layers?.length) {
  4414.             console.log("๐ŸŽจ Rendering layered pattern");
  4415.             console.log("  Uses botanical layers:", usesBotanicalLayers);
  4416.            
  4417.             const firstLayer = patternToRender.layers.find(l => !l.isShadow);
  4418.             if (firstLayer) {
  4419.                 const tempImg = new Image();
  4420.                 tempImg.crossOrigin = "Anonymous";
  4421.                 tempImg.src = normalizePath(firstLayer.path);
  4422.                
  4423.                 await new Promise((resolve) => {
  4424.                     tempImg.onload = () => {
  4425.                         const patternAspect = tempImg.width / tempImg.height;
  4426.                         const scaleMultiplier = appState.scaleMultiplier || 1;
  4427.                        
  4428.                         let patternDisplayWidth, patternDisplayHeight;
  4429.                         const baseSize = canvasSize;
  4430.                        
  4431.                         if (patternAspect > 1) {
  4432.                             patternDisplayWidth = Math.min(baseSize, canvasSize);
  4433.                             patternDisplayHeight = patternDisplayWidth / patternAspect;
  4434.                         } else {
  4435.                             patternDisplayHeight = Math.min(baseSize, canvasSize);
  4436.                             patternDisplayWidth = patternDisplayHeight * patternAspect;
  4437.                         }
  4438.                        
  4439.                         const offsetX = (canvasSize - patternDisplayWidth) / 2;
  4440.                         const offsetY = (canvasSize - patternDisplayHeight) / 2;
  4441.                        
  4442.                         previewCtx.fillStyle = backgroundColor;
  4443.                         previewCtx.fillRect(offsetX, offsetY, patternDisplayWidth, patternDisplayHeight);
  4444.                        
  4445.                         console.log(`๐ŸŽจ Pattern area: ${patternDisplayWidth.toFixed(0)}x${patternDisplayHeight.toFixed(0)}`);
  4446.                        
  4447.                         resolve({ offsetX, offsetY, patternDisplayWidth, patternDisplayHeight, scaleMultiplier });
  4448.                     };
  4449.                     tempImg.onerror = () => resolve(null);
  4450.                 }).then(async (patternBounds) => {
  4451.                     if (!patternBounds) return;
  4452.                    
  4453.                     // Render each layer with correct color mapping
  4454.                     for (let layerIndex = 0; layerIndex < patternToRender.layers.length; layerIndex++) {
  4455.                         const layer = patternToRender.layers[layerIndex];
  4456.                         const isShadow = layer.isShadow === true;
  4457.                        
  4458.                         let layerColor = null;
  4459.                         if (!isShadow) {
  4460.                             if (usesBotanicalLayers) {
  4461.     // โœ… FIX: Map botanical layer to furniture input correctly
  4462.     const furnitureInputIndex = layerMapping.patternStartIndex + layerIndex;
  4463.     layerColor = lookupColor(appState.currentLayers[furnitureInputIndex]?.color || "Snowbound");
  4464.    
  4465.     // โœ… DEBUG: Show the mapping
  4466.     const inputLayer = appState.currentLayers[furnitureInputIndex];
  4467.     console.log(`๐ŸŒฟ Botanical layer ${layerIndex} โ†’ furniture input ${furnitureInputIndex} (${inputLayer?.label}) โ†’ ${layerColor}`);
  4468.  
  4469.                             } else {
  4470.                                 // Standard mapping
  4471.                                 const inputIndex = layerMapping.patternStartIndex + layerIndex;
  4472.                                 layerColor = lookupColor(appState.currentLayers[inputIndex]?.color || "Snowbound");
  4473.                                 console.log(`๐Ÿ  Standard layer ${layerIndex} โ†’ input ${inputIndex} โ†’ ${layerColor}`);
  4474.                             }
  4475.                         }
  4476.  
  4477.                         await new Promise((resolve) => {
  4478.                             processImage(layer.path, (processedCanvas) => {
  4479.                                 if (!(processedCanvas instanceof HTMLCanvasElement)) {
  4480.                                     return resolve();
  4481.                                 }
  4482.  
  4483.                                 const patternSize = Math.max(processedCanvas.width, processedCanvas.height);
  4484.                                 const baseScale = patternBounds.patternDisplayWidth / patternSize;
  4485.                                 const finalScale = baseScale * patternBounds.scaleMultiplier;
  4486.                                 const tileWidth = processedCanvas.width * finalScale;
  4487.                                 const tileHeight = processedCanvas.height * finalScale;
  4488.  
  4489.                                 const tilingType = patternToRender.tilingType || "";
  4490.                                 const isHalfDrop = tilingType === "half-drop";
  4491.  
  4492.                                 previewCtx.save();
  4493.                                 previewCtx.beginPath();
  4494.                                 previewCtx.rect(
  4495.                                     patternBounds.offsetX,
  4496.                                     patternBounds.offsetY,
  4497.                                     patternBounds.patternDisplayWidth,
  4498.                                     patternBounds.patternDisplayHeight
  4499.                                 );
  4500.                                 previewCtx.clip();
  4501.  
  4502.                                 previewCtx.globalCompositeOperation = isShadow ? "multiply" : "source-over";
  4503.                                 previewCtx.globalAlpha = isShadow ? 0.3 : 1.0;
  4504.                                
  4505.                                 const startX = patternBounds.offsetX;
  4506.                                 const startY = patternBounds.offsetY;
  4507.                                 const endX = patternBounds.offsetX + patternBounds.patternDisplayWidth + tileWidth;
  4508.                                 const endY = patternBounds.offsetY + patternBounds.patternDisplayHeight + tileHeight;
  4509.                                
  4510.                                 for (let x = startX; x < endX; x += tileWidth) {
  4511.                                     const isOddColumn = Math.floor((x - startX) / tileWidth) % 2 !== 0;
  4512.                                     const yOffset = isHalfDrop && isOddColumn ? tileHeight / 2 : 0;
  4513.                                    
  4514.                                     for (let y = startY - tileHeight + yOffset; y < endY; y += tileHeight) {
  4515.                                         previewCtx.drawImage(processedCanvas, x, y, tileWidth, tileHeight);
  4516.                                     }
  4517.                                 }
  4518.                                
  4519.                                 previewCtx.restore();
  4520.                                 console.log(`โœ… Rendered layer ${layerIndex} with color ${layerColor}`);
  4521.                                 resolve();
  4522.                             }, layerColor, 2.2, isShadow, false, false);
  4523.                         });
  4524.                     }
  4525.                 });
  4526.             }
  4527.         }
  4528.  
  4529.         // Update DOM
  4530.         dom.preview.innerHTML = "";
  4531.         dom.preview.appendChild(previewCanvas);
  4532.         dom.preview.style.width = `${canvasSize}px`;
  4533.         dom.preview.style.height = `${canvasSize}px`;
  4534.         dom.preview.style.backgroundColor = "rgba(17, 24, 39, 1)";
  4535.  
  4536.         if (patternToRender.name) {
  4537.             dom.patternName.textContent = toInitialCaps(appState.currentPattern.name); // Keep original name
  4538.         }
  4539.        
  4540.         console.log("โœ… Pattern preview rendered");
  4541.        
  4542.     } catch (err) {
  4543.         console.error("updatePreview error:", err);
  4544.     }
  4545. };
  4546.  
  4547.  
  4548.  
  4549. // Utility: Promisified image loader
  4550. function loadImage(src) {
  4551.     return new Promise((resolve, reject) => {
  4552.         if (!src) {
  4553.             console.error("โŒ loadImage: No src provided");
  4554.             reject(new Error("No image source provided"));
  4555.             return;
  4556.         }
  4557.        
  4558.         // Normalize the path to fix ./data/ vs data/ inconsistencies
  4559.         const normalizedSrc = normalizePath(src);
  4560.         console.log(`๐Ÿ“ฅ Loading image: ${src} -> ${normalizedSrc}`);
  4561.         const img = new Image();
  4562.         img.crossOrigin = "Anonymous";
  4563.        
  4564.         img.onload = () => {
  4565.             console.log(`โœ… Image loaded successfully: ${normalizedSrc} (${img.naturalWidth}x${img.naturalHeight})`);
  4566.             resolve(img);
  4567.         };
  4568.        
  4569.         img.onerror = (error) => {
  4570.             console.error(`โŒ Failed to load image: ${normalizedSrc}`);
  4571.             console.error("โŒ Error details:", error);
  4572.             reject(new Error(`Failed to load image: ${normalizedSrc}`));
  4573.         };
  4574.        
  4575.         img.src = normalizedSrc;
  4576.     });
  4577. }
  4578.  
  4579.    
  4580. //  room mockup
  4581. let updateRoomMockup = async () => {
  4582.     try {
  4583.         if (!dom.roomMockup) {
  4584.             console.error("roomMockup element not found in DOM");
  4585.             return;
  4586.         }
  4587.  
  4588.         if (!appState.selectedCollection || !appState.currentPattern) {
  4589.             console.log("๐Ÿ” Skipping updateRoomMockup - no collection/pattern selected");
  4590.             return;
  4591.         }
  4592.  
  4593.         // Check if this is a furniture collection
  4594.         const isFurnitureCollection = appState.selectedCollection.wallMask != null;
  4595.        
  4596.         if (isFurnitureCollection) {
  4597.         console.log("๐Ÿช‘ Rendering furniture preview");
  4598.         updateFurniturePreview();
  4599.         return;
  4600.         }
  4601.  
  4602.  
  4603.  
  4604.         const isWallPanel = appState.selectedCollection?.name === "wall-panels";
  4605.  
  4606.         // ๐Ÿ” ADD THIS DEBUG HERE:
  4607.         console.log("๐Ÿ” CURRENT LAYERS MAPPING (Room Mockup):");
  4608.         appState.currentLayers.forEach((layer, index) => {
  4609.             console.log(`  ${index}: ${layer.label} = ${layer.color} (isShadow: ${layer.isShadow})`);
  4610.         });
  4611.  
  4612.  
  4613.         // ๐Ÿ” DEBUG: Check what path we're taking
  4614.         console.log("๐Ÿ” DEBUG START updateRoomMockup");
  4615.         console.log("๐Ÿ” isWallPanel:", isWallPanel);
  4616.         console.log("๐Ÿ” selectedCollection name:", appState.selectedCollection?.name);
  4617.         console.log("๐Ÿ” currentPattern.isWallPanel:", appState.currentPattern?.isWallPanel);
  4618.         console.log("๐Ÿ” currentPattern has layers:", !!appState.currentPattern?.layers?.length);
  4619.         console.log("๐Ÿ” currentPattern has tintWhite:", !!appState.currentPattern?.tintWhite);
  4620.  
  4621.        
  4622.         // Get colors from correct layer indices
  4623.         const wallColor = isWallPanel ?
  4624.             lookupColor(appState.currentLayers[0]?.color || "Snowbound") :
  4625.             lookupColor(appState.currentLayers[0]?.color || "Snowbound");
  4626.         const backgroundColor = isWallPanel ?
  4627.             lookupColor(appState.currentLayers[1]?.color || "Snowbound") :
  4628.             lookupColor(appState.currentLayers[0]?.color || "Snowbound");
  4629.        
  4630.         console.log(">>> Wall color:", wallColor, "Background color:", backgroundColor);
  4631.  
  4632.         const canvas = document.createElement("canvas");
  4633.         const ctx = canvas.getContext("2d");
  4634.         canvas.width = 600;
  4635.         canvas.height = 450;
  4636.         console.log(`๐ŸŽจ Room mockup canvas created: ${canvas.width}x${canvas.height}`);
  4637.  
  4638.         const processOverlay = async () => {
  4639.             console.log("๐Ÿ” processOverlay() START");
  4640.             // Fill wall color
  4641.             ctx.fillStyle = wallColor;
  4642.             ctx.fillRect(0, 0, canvas.width, canvas.height);
  4643.             console.log("๐Ÿ” Wall color filled");
  4644.  
  4645.             if (isWallPanel && appState.currentPattern?.layers?.length) {
  4646.                     console.log("๐Ÿ” TAKING PATH: Wall panel processing");
  4647.  
  4648.                 // Handle wall panel rendering
  4649.                 const panelWidthInches = appState.currentPattern.size[0] || 24;
  4650.                 const panelHeightInches = appState.currentPattern.size[1] || 36;
  4651.                 const scale = Math.min(canvas.width / 100, canvas.height / 80) * (appState.scaleMultiplier || 1);
  4652.                
  4653.                 const panelWidth = panelWidthInches * scale;
  4654.                 const panelHeight = panelHeightInches * scale;
  4655.                
  4656.                 const layout = appState.currentPattern.layout || "3,20";
  4657.                 const [numPanelsStr, spacingStr] = layout.split(",");
  4658.                 const numPanels = parseInt(numPanelsStr, 10) || 3;
  4659.                 const spacing = parseInt(spacingStr, 10) || 20;
  4660.                
  4661.                 const totalWidth = (numPanels * panelWidth) + ((numPanels - 1) * spacing);
  4662.                 const startX = (canvas.width - totalWidth) / 2;
  4663.                 const startY = (canvas.height - panelHeight) / 2 - (appState.currentPattern?.verticalOffset || 50);
  4664.  
  4665.                 // Create panel canvas
  4666.                 const panelCanvas = document.createElement("canvas");
  4667.                 panelCanvas.width = panelWidth;
  4668.                 panelCanvas.height = panelHeight;
  4669.                 const panelCtx = panelCanvas.getContext("2d");
  4670.  
  4671.                 // Process panel layers - find input layers only
  4672.                 let currentLayerIndex = 0; // Start from first design layer
  4673.  
  4674.                 for (let i = 0; i < appState.currentPattern.layers.length; i++) {
  4675.                     const layer = appState.currentPattern.layers[i];
  4676.                     const isShadow = layer.isShadow === true;
  4677.                    
  4678.                         console.log(`๐Ÿ” LAYER DEBUG ${i}:`, {
  4679.                         path: layer.path,
  4680.                         isShadow: isShadow,
  4681.                         currentLayerIndex: currentLayerIndex,
  4682.                         expectedColorIndex: currentLayerIndex + 2
  4683.                     });
  4684.  
  4685.                     let layerColor = null;
  4686.                     if (!isShadow) {
  4687.                         // Wall panels: [0: Wall, 1: Background, 2+: Design layers]
  4688.                         const colorLayerIndex = currentLayerIndex + 2; // Skip wall (0) and background (1)
  4689.                         layerColor = lookupColor(appState.currentLayers[colorLayerIndex]?.color || "Snowbound");
  4690.                         console.log(`๐ŸŽจ Regular layer ${i}: using color from currentLayers[${colorLayerIndex}] = ${layerColor}`);
  4691.                         currentLayerIndex++;
  4692.                     }
  4693.                    
  4694.                     await new Promise((resolve) => {
  4695.                         console.log(`๐Ÿ” About to call processImage with: isShadow=${isShadow}, isWallPanel=true, isWall=false`);
  4696.  
  4697.                         processImage(layer.path, (processedCanvas) => {
  4698.                             if (processedCanvas instanceof HTMLCanvasElement) {
  4699.                                 panelCtx.globalCompositeOperation = isShadow ? "multiply" : "source-over";
  4700.                                 panelCtx.globalAlpha = isShadow ? 0.3 : 1.0;
  4701.                                 panelCtx.drawImage(processedCanvas, 0, 0, panelWidth, panelHeight);
  4702.                             }
  4703.                             resolve();
  4704.                         }, layerColor, 2.2, isShadow, true, false);
  4705.                     });
  4706.                 }
  4707.                    
  4708.  
  4709.                 // Draw panels
  4710.                 for (let i = 0; i < numPanels; i++) {
  4711.                     const x = startX + (i * (panelWidth + spacing));
  4712.                     ctx.fillStyle = backgroundColor;
  4713.                     ctx.fillRect(x, startY, panelWidth, panelHeight);
  4714.                     ctx.drawImage(panelCanvas, x, startY, panelWidth, panelHeight);
  4715.                 }
  4716.             } else {
  4717.                     console.log("๐Ÿ” TAKING PATH: Regular pattern processing");
  4718.                     console.log("๐Ÿ” appState.currentPattern:", appState.currentPattern);
  4719.                     console.log("๐Ÿ” appState.currentPattern.layers:", appState.currentPattern?.layers);
  4720.  
  4721.                 // Handle regular pattern rendering
  4722.                 const patternCanvas = document.createElement("canvas");
  4723.                 patternCanvas.width = canvas.width;
  4724.                 patternCanvas.height = canvas.height;
  4725.                 const patternCtx = patternCanvas.getContext("2d");
  4726.                
  4727.                 if (appState.currentPattern?.tintWhite && appState.currentPattern?.baseComposite) {
  4728.                             console.log("๐Ÿ” TAKING SUBPATH: Tint white");
  4729.  
  4730.                     // Handle tint white in room mockup
  4731.                     const baseImage = new Image();
  4732.                     baseImage.src = normalizePath(appState.currentPattern.baseComposite);
  4733.                    
  4734.                     await new Promise((resolve) => {
  4735.                         baseImage.onload = () => {
  4736.                             const scale = (appState.currentScale / 100 || 1) * (appState.scaleMultiplier || 1);
  4737.                             const tileWidth = baseImage.width * scale;
  4738.                             const tileHeight = baseImage.height * scale;
  4739.                            
  4740.                             // Tile pattern
  4741.                             for (let x = -tileWidth; x < canvas.width + tileWidth; x += tileWidth) {
  4742.                                 for (let y = -tileHeight; y < canvas.height + tileHeight; y += tileHeight) {
  4743.                                     patternCtx.drawImage(baseImage, x, y, tileWidth, tileHeight);
  4744.                                 }
  4745.                             }
  4746.                            
  4747.                             // Apply tint (with CORS protection)
  4748.                             let imageData;
  4749.                             try {
  4750.                                 imageData = patternCtx.getImageData(0, 0, canvas.width, canvas.height);
  4751.                             } catch (e) {
  4752.                                 console.warn("โš ๏ธ Canvas tainted, skipping tint white effect:", e.message);
  4753.                                 ctx.drawImage(patternCanvas, 0, 0);
  4754.                                 return;
  4755.                             }
  4756.                             const data = imageData.data;
  4757.                             const hex = wallColor.replace("#", "");
  4758.                             const rTint = parseInt(hex.substring(0, 2), 16);
  4759.                             const gTint = parseInt(hex.substring(2, 4), 16);
  4760.                             const bTint = parseInt(hex.substring(4, 6), 16);
  4761.                            
  4762.                             for (let i = 0; i < data.length; i += 4) {
  4763.                                 const r = data[i], g = data[i + 1], b = data[i + 2];
  4764.                                 if (r > 240 && g > 240 && b > 240) {
  4765.                                     data[i] = rTint;
  4766.                                     data[i + 1] = gTint;
  4767.                                     data[i + 2] = bTint;
  4768.                                 }
  4769.                             }
  4770.                            
  4771.                             patternCtx.putImageData(imageData, 0, 0);
  4772.                             ctx.drawImage(patternCanvas, 0, 0);
  4773.                             resolve();
  4774.                         };
  4775.                         baseImage.onerror = resolve;
  4776.                     });
  4777.                     } else if (appState.currentPattern?.layers?.length && !isWallPanel) {
  4778.                                 console.log("๐Ÿ” TAKING SUBPATH: Regular layers");
  4779.  
  4780.                     // Handle regular layered patterns - FIXED indexing
  4781.                     let currentLayerIndex = 0; // Start from first non-shadow layer
  4782.                    
  4783.                     const inputLayers = appState.currentLayers.filter(layer => !layer.isShadow);
  4784.                     let inputLayerIndex = 0;
  4785.  
  4786.                     for (let i = 0; i < appState.currentPattern.layers.length; i++) {
  4787.                         const layer = appState.currentPattern.layers[i];
  4788.                         const isShadow = layer.isShadow === true;
  4789.                        
  4790.                         let layerColor = null;
  4791.                         if (!isShadow) {
  4792.                             const inputLayer = inputLayers[inputLayerIndex + 1]; // Skip background
  4793.                             layerColor = lookupColor(inputLayer?.color || "Snowbound");
  4794.                             inputLayerIndex++; // Increment here
  4795.                         }
  4796.  
  4797.                     // Check for half-drop tiling (declare once, outside)
  4798.                     const tilingType = appState.currentPattern.tilingType || "";
  4799.                     const isHalfDrop = tilingType === "half-drop";
  4800.                     console.log(`๐Ÿ”„ ROOM MOCKUP Tiling type: ${tilingType}, Half-drop: ${isHalfDrop}`);
  4801.  
  4802.                     await new Promise((resolve) => {
  4803.                         processImage(layer.path, (processedCanvas) => {
  4804.                             if (processedCanvas instanceof HTMLCanvasElement) {
  4805.                                 const scale = (appState.currentScale / 100 || 1) * (appState.scaleMultiplier || 1);
  4806.                                 const tileWidth = processedCanvas.width * scale;
  4807.                                 const tileHeight = processedCanvas.height * scale;
  4808.                                
  4809.                                 patternCtx.globalCompositeOperation = isShadow ? "multiply" : "source-over";
  4810.                                 patternCtx.globalAlpha = isShadow ? 0.3 : 1.0;
  4811.                                
  4812.                                 for (let x = -tileWidth; x < canvas.width + tileWidth; x += tileWidth) {
  4813.                                     const isOddColumn = Math.floor((x + tileWidth) / tileWidth) % 2 !== 0;
  4814.                                     const yOffset = isHalfDrop && isOddColumn ? tileHeight / 2 : 0;
  4815.                                     console.log(`๐Ÿ”„ Column at x=${x}, isOdd=${isOddColumn}, yOffset=${yOffset}`);
  4816.                                    
  4817.                                     for (let y = -tileHeight + yOffset; y < canvas.height + tileHeight; y += tileHeight) {
  4818.                                         patternCtx.drawImage(processedCanvas, x, y, tileWidth, tileHeight);
  4819.                                     }
  4820.                                 }
  4821.                                 console.log(`โœ… Regular layer ${i} rendered with color ${layerColor}`);
  4822.                             }
  4823.                             resolve();
  4824.                         }, layerColor, 2.2, isShadow, false, false);
  4825.                     });
  4826.                        
  4827.                     }
  4828.                    
  4829.                     ctx.drawImage(patternCanvas, 0, 0);
  4830.                     console.log("๐Ÿ” Pattern canvas drawn to main canvas");
  4831.                 }
  4832.             }
  4833.             console.log("๐Ÿ” Finished pattern processing, moving to collection mockup check");
  4834.  
  4835.             console.log("๐Ÿ” Full selectedCollection:", Object.keys(appState.selectedCollection));
  4836.             console.log("๐Ÿ” selectedCollection object:", appState.selectedCollection);
  4837.             console.log("๐Ÿ” selectedCollection.mockup:", appState.selectedCollection?.mockup);
  4838.             console.log("๐Ÿ” selectedCollection.mockupShadow:", appState.selectedCollection?.mockupShadow);
  4839.  
  4840.  
  4841.             // Apply mockup overlay if exists
  4842.             if (appState.selectedCollection?.mockup) {
  4843.                 const originalPath = appState.selectedCollection.mockup;
  4844.                 const normalizedPath = normalizePath(originalPath);
  4845.                 console.log(`๐Ÿ  Loading collection mockup:`);
  4846.                 console.log(`  Original: ${originalPath}`);
  4847.                 console.log(`  Normalized: ${normalizedPath}`);
  4848.                 const mockupImage = new Image();
  4849.                 mockupImage.crossOrigin = "Anonymous";
  4850.                 mockupImage.src = normalizedPath;
  4851.                
  4852.                 await new Promise((resolve) => {
  4853.                     mockupImage.onload = () => {
  4854.                         console.log(`โœ… Collection mockup loaded: ${mockupImage.width}x${mockupImage.height}`);
  4855.                         const fit = scaleToFit(mockupImage, canvas.width, canvas.height);
  4856.                         ctx.drawImage(mockupImage, fit.x, fit.y, fit.width, fit.height);
  4857.                         console.log(`๐Ÿ“ Mockup drawn at: ${fit.x}, ${fit.y}, ${fit.width}x${fit.height}`);
  4858.                        
  4859.                         console.log("๐Ÿ” selectedCollection:", appState.selectedCollection?.name);
  4860.                         console.log("๐Ÿ” selectedCollection.elements:", appState.selectedCollection?.elements);
  4861.                         resolve();
  4862.                     };
  4863.                     mockupImage.onerror = (e) => {
  4864.                         console.error(`โŒ Failed to load collection mockup: ${normalizedPath}`, e);
  4865.                         console.error(`โŒ Actual URL that failed: ${mockupImage.src}`);
  4866.                         resolve();
  4867.                     };
  4868.                 });
  4869.             }
  4870.  
  4871.             // Apply shadow overlay if exists
  4872.             if (appState.selectedCollection?.mockupShadow) {
  4873.                 const shadowOriginalPath = appState.selectedCollection.mockupShadow;
  4874.                 const shadowNormalizedPath = normalizePath(shadowOriginalPath);
  4875.                 console.log(`๐ŸŒซ๏ธ Loading collection shadow:`);
  4876.                 console.log(`  Original: ${shadowOriginalPath}`);
  4877.                 console.log(`  Normalized: ${shadowNormalizedPath}`);
  4878.                 const shadowOverlay = new Image();
  4879.                 shadowOverlay.crossOrigin = "Anonymous";
  4880.                 shadowOverlay.src = shadowNormalizedPath;
  4881.                
  4882.                 await new Promise((resolve) => {
  4883.                     shadowOverlay.onload = () => {
  4884.                         console.log(`โœ… Collection shadow loaded: ${shadowOverlay.width}x${shadowOverlay.height}`);
  4885.                         ctx.globalCompositeOperation = "multiply";
  4886.                         const fit = scaleToFit(shadowOverlay, canvas.width, canvas.height);
  4887.                         ctx.drawImage(shadowOverlay, fit.x, fit.y, fit.width, fit.height);
  4888.                         console.log(`๐ŸŒซ๏ธ Shadow drawn at: ${fit.x}, ${fit.y}, ${fit.width}x${fit.height}`);
  4889.                         ctx.globalCompositeOperation = "source-over";
  4890.                         resolve();
  4891.                     };
  4892.                     shadowOverlay.onerror = (e) => {
  4893.                         console.error(`โŒ Failed to load shadow overlay: ${shadowNormalizedPath}`, e);
  4894.                         console.error(`โŒ Actual shadow URL that failed: ${shadowOverlay.src}`);
  4895.                         resolve();
  4896.                     };
  4897.                 });
  4898.             } else {
  4899.                 console.warn("โš ๏ธ No mockup found for collection:", appState.selectedCollection?.name);
  4900.                 console.log("๐Ÿ” Available collection properties:", Object.keys(appState.selectedCollection || {}));
  4901.             }
  4902.  
  4903.             // Render final canvas with CORS error handling
  4904.             let dataUrl;
  4905.             try {
  4906.                 dataUrl = canvas.toDataURL("image/png");
  4907.                 console.log("โœ… Room mockup canvas exported successfully");
  4908.             } catch (e) {
  4909.                 if (e.name === 'SecurityError') {
  4910.                     console.log("๐Ÿ›ก๏ธ Room mockup CORS error - using canvas directly in DOM");
  4911.                     // Instead of using dataURL, append the canvas directly
  4912.                     canvas.style.cssText = "width: 100%; height: 100%; object-fit: contain; border: 1px solid #333;";
  4913.                     dom.roomMockup.innerHTML = "";
  4914.                     dom.roomMockup.appendChild(canvas);
  4915.                     console.log("โœ… Room mockup canvas appended directly to DOM");
  4916.                     ensureButtonsAfterUpdate();
  4917.                     // Reset all styling including background from fabric mode
  4918.                     dom.roomMockup.style.cssText = "width: 600px; height: 450px; position: relative; background-image: none; background-color: #434341;";
  4919.                     return; // Exit early, don't create img element
  4920.                 }
  4921.                 throw e; // Re-throw non-CORS errors
  4922.             }
  4923.            
  4924.             const img = document.createElement("img");
  4925.             img.src = dataUrl;
  4926.             img.style.cssText = "width: 100%; height: 100%; object-fit: contain; border: 1px solid #333;";
  4927.            
  4928.             img.onload = () => {
  4929.                 console.log("โœ… Room mockup image loaded successfully");
  4930.             };
  4931.             img.onerror = (e) => {
  4932.                 console.error("โŒ Room mockup image failed to load:", e);
  4933.             };
  4934.            
  4935.             dom.roomMockup.innerHTML = "";
  4936.             dom.roomMockup.appendChild(img);
  4937.             console.log("โœ… Room mockup image appended to DOM");
  4938.             ensureButtonsAfterUpdate();
  4939.             dom.roomMockup.style.cssText = "width: 600px; height: 450px; position: relative; background: #434341;";
  4940.         };
  4941.  
  4942.         await processOverlay().catch(error => {
  4943.             console.error("Error processing room mockup:", error);
  4944.         });
  4945.  
  4946.     } catch (e) {
  4947.         console.error('Error in updateRoomMockup:', e);
  4948.     }
  4949. };
  4950. // GUARD / TRACE WRAPPER
  4951. if (USE_GUARD && DEBUG_TRACE) {
  4952. updateRoomMockup = guard(traceWrapper(updateRoomMockup, "updateRoomMockup")); // Wrapped for debugging
  4953. } else if (USE_GUARD) {
  4954.     updateRoomMockup = guard(updateRoomMockup, "updateRoomMockup"); // Wrapped for debugging
  4955. }
  4956.  
  4957. const updateFurniturePreview = async () => {
  4958. // Add this at the start of updateFurniturePreview()
  4959. const layerMapping = getLayerMappingForPreview(true);
  4960. console.log("๐Ÿ” LAYER MAPPING DEBUG IN FURNITURE PREVIEW:");
  4961. console.log("  wallIndex:", layerMapping.wallIndex);
  4962. console.log("  backgroundIndex:", layerMapping.backgroundIndex);  
  4963. console.log("  patternStartIndex:", layerMapping.patternStartIndex);
  4964. console.log("  Expected: wallIndex=0, backgroundIndex=1, patternStartIndex=2");
  4965.  
  4966.     try {
  4967.         console.log("๐Ÿ›‹๏ธ =========================");
  4968.         console.log("๐Ÿ›‹๏ธ Starting furniture preview");
  4969.         console.log("๐Ÿ›‹๏ธ =========================");
  4970.  
  4971.             const frozenZoomState = {
  4972.             scale: furnitureViewSettings.scale,
  4973.             offsetX: furnitureViewSettings.offsetX,
  4974.             offsetY: furnitureViewSettings.offsetY,
  4975.             isZoomed: furnitureViewSettings.isZoomed,
  4976.             timestamp: Date.now()
  4977.         };
  4978.        
  4979.         console.log("๐Ÿ”’ FROZEN zoom state for all layers:", frozenZoomState);
  4980.  
  4981.        
  4982.         // ๐Ÿ” ADD THIS DEBUG LINE:
  4983.         console.log("๐Ÿ” ENTRY POINT - Current furnitureViewSettings:", JSON.stringify(furnitureViewSettings, null, 2));
  4984.        
  4985.         // โœ… PRESERVE ZOOM SETTINGS ONCE AT THE START
  4986.         const preservedSettings = {
  4987.             scale: furnitureViewSettings.scale,
  4988.             offsetX: furnitureViewSettings.offsetX,
  4989.             offsetY: furnitureViewSettings.offsetY,
  4990.             isZoomed: furnitureViewSettings.isZoomed
  4991.         };
  4992.  
  4993.        
  4994.         console.log("๐Ÿ”’ Preserved zoom settings:", preservedSettings);
  4995.  
  4996.         // Basic validation
  4997.         if (!dom.roomMockup) {
  4998.             console.error("โŒ roomMockup element not found in DOM");
  4999.             return;
  5000.         }
  5001.  
  5002.         if (!appState.currentPattern) {
  5003.             console.error("โŒ No current pattern selected");
  5004.             return;
  5005.         }
  5006.  
  5007.         // Ensure furniture config is loaded
  5008.         if (!furnitureConfig) {
  5009.             console.log("๐Ÿ”„ Loading furniture config...");
  5010.             await loadFurnitureConfig();
  5011.         }
  5012.  
  5013.         if (!furnitureConfig) {
  5014.             console.error("โŒ furnitureConfig still not loaded after attempt");
  5015.             return;
  5016.         }
  5017.  
  5018.         // Setup canvas
  5019.         const canvas = document.createElement("canvas");
  5020.         canvas.width = 600;
  5021.         canvas.height = 450;
  5022.         const ctx = canvas.getContext("2d");
  5023.  
  5024.         // Get collection and pattern data
  5025.         const collection = appState.selectedCollection;
  5026.         const pattern = appState.currentPattern;
  5027.         const furnitureType = collection?.furnitureType || 'sofa-capitol';
  5028.         const furniture = furnitureConfig?.[furnitureType];
  5029.  
  5030.         // Debug furniture config
  5031.         console.log("๐Ÿ” FURNITURE CONFIG DEBUG:");
  5032.         console.log("  Collection name:", collection?.name);
  5033.         console.log("  Furniture type:", furnitureType);
  5034.         console.log("  Available furniture configs:", Object.keys(furnitureConfig || {}));
  5035.         console.log("  Selected furniture config exists:", !!furniture);
  5036.  
  5037.         if (!furniture) {
  5038.             console.error("โŒ No furniture config found for:", furnitureType);
  5039.             console.log("Available configs:", Object.keys(furnitureConfig));
  5040.             return;
  5041.         }
  5042.  
  5043.         // Debug furniture paths
  5044.         console.log("๐Ÿ” FURNITURE PATHS DEBUG:");
  5045.         console.log("  Mockup path:", furniture.mockup);
  5046.         console.log("  Wall mask path:", furniture.wallMask);
  5047.         console.log("  Base path:", furniture.base);
  5048.         console.log("  Extras path:", furniture.extras);
  5049.  
  5050.         // Test if files exist
  5051.         const testPaths = [
  5052.             { name: "mockup", path: furniture.mockup },
  5053.             { name: "wallMask", path: furniture.wallMask },
  5054.             { name: "base", path: furniture.base },
  5055.             { name: "extras", path: furniture.extras }
  5056.         ];
  5057.  
  5058.         console.log("๐Ÿ” TESTING FILE EXISTENCE:");
  5059.         testPaths.forEach(({ name, path }) => {
  5060.             if (path) {
  5061.                 const testImg = new Image();
  5062.                 testImg.onload = () => console.log(`โœ… ${name} file exists: ${path}`);
  5063.                 testImg.onerror = () => console.log(`โŒ ${name} file MISSING: ${path}`);
  5064.                 testImg.src = normalizePath(path);
  5065.             } else {
  5066.                 console.log(`โš ๏ธ ${name} path not defined in config`);
  5067.             }
  5068.         });
  5069.  
  5070.         // Get layer mapping for furniture collection
  5071.         const layerMapping = getLayerMappingForPreview(true); // Always true for furniture
  5072.         console.log("๐Ÿ” LAYER MAPPING DEBUG:");
  5073.         console.log("  Layer mapping:", layerMapping);
  5074.         console.log("  Total current layers:", appState.currentLayers.length);
  5075.  
  5076.         // Debug current layer assignments
  5077.         console.log("๐Ÿ” CURRENT LAYER ASSIGNMENTS:");
  5078.         appState.currentLayers.forEach((layer, index) => {
  5079.             let usage = "unused";
  5080.             if (index === layerMapping.wallIndex) usage = "wall color";
  5081.             else if (index === layerMapping.backgroundIndex) usage = "sofa base color";
  5082.             else if (index >= layerMapping.patternStartIndex) usage = `pattern layer ${index - layerMapping.patternStartIndex}`;
  5083.            
  5084.             console.log(`  ${index}: ${layer.label} = "${layer.color}" (${usage})`);
  5085.         });
  5086.  
  5087.         // Clear canvas with white background
  5088.         ctx.fillStyle = "transparent";
  5089.         ctx.fillRect(0, 0, 600, 450);
  5090.         console.log("๐Ÿงน Canvas cleared with white background");
  5091.         ctx.clearRect(0, 0, 600, 450);
  5092.         ctx.fillStyle = "#F5F5F5";
  5093.         ctx.fillRect(0, 0, 600, 450);
  5094.  
  5095.  
  5096.  
  5097.         // โŒ REMOVED: The problematic settings update that was resetting zoom
  5098.         // NO LONGER UPDATING furnitureViewSettings here - using preserved settings
  5099.  
  5100.         console.log("๐Ÿ” FURNITURE VIEW SETTINGS:");
  5101.         console.log("  Scale:", furnitureViewSettings.scale);
  5102.         console.log("  Offset X:", furnitureViewSettings.offsetX);
  5103.         console.log("  Offset Y:", furnitureViewSettings.offsetY);
  5104.  
  5105.         try {
  5106.         console.log("๐Ÿ—๏ธ =========================");
  5107.         console.log("๐Ÿ—๏ธ FURNITURE RENDERING SEQUENCE (WITH WALL MASK)");
  5108.         console.log("๐Ÿ—๏ธ =========================");
  5109.        
  5110.         // ===== STEP 1: Draw room mockup base =====
  5111.         console.log("1๏ธโƒฃ Drawing mockup base (room scene)");
  5112.         const mockupPath = furniture.mockup;
  5113.         if (mockupPath) {
  5114.             console.log("  Mockup path:", mockupPath);
  5115.             await drawFurnitureLayer(ctx, mockupPath).catch(err => {
  5116.                 console.error("โŒ Failed to load mockup:", err);
  5117.                 zoomState: frozenZoomState
  5118.                 ctx.fillStyle = "#E5E7EB";
  5119.                 ctx.fillRect(0, 0, 600, 450);
  5120.                 console.log("๐Ÿ”„ Drew fallback background due to mockup failure");
  5121.             });
  5122.             console.log("โœ… Room mockup base drawn");
  5123.         } else {
  5124.             console.error("โŒ No mockup path in furniture config");
  5125.             ctx.fillStyle = "#E5E7EB";
  5126.             ctx.fillRect(0, 0, 600, 450);
  5127.         }
  5128.        
  5129.         // ===== STEP 2: Draw wall color using wall mask =====
  5130.         console.log("2๏ธโƒฃ Drawing wall color via mask");
  5131.         const wallColor = resolveColor(appState.currentLayers[layerMapping.wallIndex]?.color || "Snowbound");
  5132.         console.log(`  Wall color from input ${layerMapping.wallIndex}: ${wallColor}`);
  5133.        
  5134.         if (furniture.wallMask) {
  5135.             console.log("  Wall mask path:", furniture.wallMask);
  5136.             await drawFurnitureLayer(ctx, furniture.wallMask, {
  5137.                 tintColor: wallColor,
  5138.                 isMask: true,
  5139.                 zoomState: frozenZoomState
  5140.             });
  5141.             console.log("โœ… Wall color applied via mask");
  5142.         } else {
  5143.             console.error("โŒ No wallMask path in furniture config");
  5144.             console.log("  Available furniture config keys:", Object.keys(furniture));
  5145.         }
  5146.  
  5147.         // TEST: Try to load the wall mask image manually
  5148.         console.log("๐Ÿงช TESTING WALL MASK IMAGE LOAD:");
  5149.         try {
  5150.             const testMaskImg = new Image();
  5151.             testMaskImg.onload = () => {
  5152.                 console.log(`โœ… Wall mask loaded successfully: ${furniture.wallMask}`);
  5153.                 console.log(`  Dimensions: ${testMaskImg.naturalWidth}x${testMaskImg.naturalHeight}`);
  5154.                 console.log(`  Image appears valid for masking`);
  5155.             };
  5156.             testMaskImg.onerror = (err) => {
  5157.                 console.log(`โŒ Wall mask failed to load: ${furniture.wallMask}`);
  5158.                 console.log(`  Error:`, err);
  5159.                 console.log(`  This is why wall color fills entire canvas!`);
  5160.             };
  5161.             testMaskImg.src = normalizePath(furniture.wallMask);
  5162.         } catch (e) {
  5163.             console.log(`โŒ Error testing wall mask: ${e.message}`);
  5164.         }
  5165.  
  5166.         // ===== STEP 3: Draw sofa base =====
  5167.         console.log("3๏ธโƒฃ Drawing sofa base - USING MAPPING");
  5168.  
  5169.         // โœ… Use the layer mapping to get the correct background index
  5170.         const backgroundIndex = layerMapping.backgroundIndex;
  5171.  
  5172.        
  5173.         const backgroundLayer = appState.currentLayers[backgroundIndex];
  5174.         const sofaBaseColor = resolveColor(backgroundLayer?.color || "#FAFAFA");
  5175.  
  5176.         // โœ… ENHANCED DEBUG - Let's catch the bug red-handed
  5177.         console.log("๐Ÿ” SOFA BASE COLOR RESOLUTION DEBUG:");
  5178.         console.log("  backgroundIndex:", backgroundIndex);
  5179.         console.log("  backgroundLayer:", backgroundLayer);
  5180.         console.log("  backgroundLayer.label:", backgroundLayer?.label);
  5181.         console.log("  backgroundLayer.color:", backgroundLayer?.color);
  5182.         console.log("  sofaBaseColor resolved to:", sofaBaseColor);
  5183.  
  5184.         // โœ… ALSO CHECK: What does resolveColor actually return?
  5185.         console.log("  resolveColor direct test:", resolveColor(backgroundLayer?.color));
  5186.         console.log("  lookupColor direct test:", lookupColor(backgroundLayer?.color));
  5187.  
  5188.  
  5189.         console.log(`  Sofa base color from input ${backgroundIndex} (${appState.currentLayers[backgroundIndex]?.label}): ${sofaBaseColor}`);
  5190.  
  5191.         if (furniture.base) {
  5192.             console.log("  ๐Ÿ›‹๏ธ Sofa base path exists:", furniture.base);
  5193.             console.log("  ๐Ÿ›‹๏ธ Calling drawFurnitureLayer for sofa base...");
  5194.            
  5195.             // โœ… ENSURE SOFA BASE COMPLETES BEFORE PATTERNS
  5196.             console.log("๐Ÿ› ABOUT TO DRAW SOFA BASE:");
  5197.         console.log("  furniture.base path:", furniture.base);
  5198.         console.log("  Should be: data/furniture/sofa-capitol/sofa-capitol-base.png");
  5199.         console.log("  Tint color:", sofaBaseColor);
  5200.  
  5201.             try {
  5202.                 await drawFurnitureLayer(ctx, furniture.base, {
  5203.                     tintColor: sofaBaseColor,
  5204.                     zoomState: frozenZoomState
  5205.                 });
  5206.                 console.log("  โœ… Sofa base step completed - CONFIRMED");
  5207.             } catch (error) {
  5208.                 console.error("  โŒ Sofa base failed:", error);
  5209.             }
  5210.  
  5211.                 // โœ… Then: Add shadow layer with multiply blend (no UI input needed)
  5212.                 const shadowPath = furniture.baseShadow || furniture.base.replace('.png', '-shadow.png');
  5213.                 console.log("  ๐ŸŒš Adding sofa base shadow...");
  5214.                
  5215.                 await drawFurnitureLayer(ctx, shadowPath, {
  5216.                     tintColor: null,  // No tinting for shadow
  5217.                     zoomState: frozenZoomState,
  5218.                     blendMode: "multiply",  // Multiply blend for shadow
  5219.                     opacity: 0.7  // Adjust shadow intensity
  5220.                 });
  5221.                 console.log("  โœ… Sofa base shadow completed");
  5222.  
  5223.         } else {
  5224.             console.error("โŒ No base path in furniture config");
  5225.         }
  5226.  
  5227.         // โœ… ADD DELAY TO ENSURE SOFA BASE IS FULLY RENDERED
  5228.         console.log("โณ Waiting for sofa base to complete before patterns...");
  5229.         await new Promise(resolve => setTimeout(resolve, 50));
  5230.    
  5231.  
  5232.             // ===== STEP 4: Draw pattern layers =====
  5233.             console.log("4๏ธโƒฃ Drawing pattern layers - ENHANCED DEBUG");
  5234.             console.log(`  Total pattern layers to process: ${pattern.layers.length}`);
  5235.             console.log(`  Pattern layer start index: ${layerMapping.patternStartIndex}`);
  5236.             console.log(`  Available inputs: ${appState.currentLayers.length}`);
  5237.  
  5238.             // Show all current inputs
  5239.             console.log("  ๐Ÿ“‹ ALL CURRENT INPUTS:");
  5240.             appState.currentLayers.forEach((layer, idx) => {
  5241.                 console.log(`    Input ${idx}: ${layer.label} = "${layer.color}"`);
  5242.             });
  5243.  
  5244.             console.log("  ๐ŸŽจ PATTERN LAYER MAPPING:");
  5245.             for (let i = 0; i < pattern.layers.length; i++) {
  5246.  
  5247.                 const layer = pattern.layers[i];
  5248.                 const furnitureInputIndex = layerMapping.patternStartIndex + i;
  5249.                 const inputLayer = appState.currentLayers[furnitureInputIndex];
  5250.                 const layerColor = resolveColor(inputLayer?.color || "Snowbound");
  5251.                
  5252.                 console.log(`  ๐Ÿ“ Pattern layer ${i}:`);
  5253.                 console.log(`    Layer path: ${layer.path?.split('/').pop()}`);
  5254.                 console.log(`    Maps to input ${furnitureInputIndex}: ${inputLayer?.label} = "${inputLayer?.color}"`);
  5255.                 console.log(`    Resolved color: ${layerColor}`);
  5256.                 console.log(`    Input exists: ${!!inputLayer}`);
  5257.                
  5258.                 if (layerColor && layer.path) {
  5259.                     try {
  5260.                         console.log(`    ๐ŸŽจ Using processImage for pattern layer ${i} with color ${layerColor}`);
  5261.                        
  5262.                 if (layerColor && layer.path) {
  5263.                     try {
  5264.                         await drawFurnitureLayer(ctx, layer.path, {
  5265.                             tintColor: layerColor,
  5266.                             zoomState: frozenZoomState,
  5267.                             highRes: true  // โœ… Enable high-res for patterns
  5268.  
  5269.                         });
  5270.                         console.log(`    โœ… Pattern layer ${i} rendered in high resolution`);
  5271.                     } catch (error) {
  5272.                         console.error(`    โŒ Failed to render pattern layer ${i}:`, error);
  5273.                     }
  5274.                 }        
  5275.                     } catch (error) {
  5276.                         console.error(`    โŒ Failed to render pattern layer ${i}:`, error);
  5277.                     }
  5278.                 } else {
  5279.                     console.warn(`    โš ๏ธ Skipping pattern layer ${i}: missing color or path`);
  5280.                 }
  5281.             }        
  5282.             console.log("โœ… Pattern layers step completed");
  5283.  
  5284.             // โœ… NEW STEP 4.5: Add sofa base shadow AFTER patterns
  5285.             console.log("4๏ธโƒฃ.5 Adding sofa base shadow on top of patterns");
  5286.  
  5287.             const shadowPath = furniture.baseShadow || furniture.base.replace('.png', '-shadow.png');
  5288.             if (shadowPath && furniture.base) {
  5289.                 console.log("  ๐ŸŒš Drawing shadow on top of patterns...");
  5290.                
  5291.                 try {
  5292.                     await drawFurnitureLayer(ctx, shadowPath, {
  5293.                         tintColor: null,  // No tinting for shadow
  5294.                         zoomState: frozenZoomState,
  5295.                         blendMode: "multiply",  // Multiply blend for shadow effect
  5296.                         opacity: 0.7  // Adjust shadow intensity as needed
  5297.                     });
  5298.                     console.log("  โœ… Shadow applied on top of patterns");
  5299.                 } catch (error) {
  5300.                     console.log("  โš ๏ธ Shadow file not found, skipping:", shadowPath);
  5301.                 }
  5302.             } else {
  5303.                 console.log("  โš ๏ธ No shadow path defined, skipping shadow");
  5304.             }
  5305.  
  5306.            
  5307.             // ===== STEP 5: Draw extras on top =====
  5308.             console.log("5๏ธโƒฃ Drawing extras");
  5309.             if (furniture.extras) {
  5310.                 console.log("  Extras path:", furniture.extras);
  5311.                 console.log("  Drawing extras without tint (natural colors)");
  5312.                
  5313.                 try {
  5314.                     await drawFurnitureLayer(ctx, furniture.extras, {
  5315.                         tintColor: null,
  5316.                         zoomState: frozenZoomState,
  5317.                         opacity: 1.0,
  5318.                         blendMode: "source-over"
  5319.                     });
  5320.                     console.log("โœ… Extras step completed");
  5321.                 } catch (error) {
  5322.                     console.error("โŒ Failed to draw extras:", error);
  5323.                 }
  5324.             } else {
  5325.                 console.warn("โš ๏ธ No extras defined in furniture config");
  5326.             }
  5327.            
  5328.             console.log("๐ŸŽ‰ =========================");
  5329.             console.log("๐ŸŽ‰ FURNITURE RENDERING COMPLETE (WITH WALL MASK)");
  5330.             console.log("๐ŸŽ‰ =========================");
  5331.  
  5332.            
  5333.             // ===== STEP 6: Display result =====
  5334.             console.log("6๏ธโƒฃ Displaying result");
  5335.             const dataUrl = canvas.toDataURL("image/png");
  5336.             const img = document.createElement("img");
  5337.             img.src = dataUrl;
  5338.             img.style.cssText = "width: 100%; height: 100%; object-fit: contain;";
  5339.            
  5340.             // Clear and append to DOM
  5341.             dom.roomMockup.innerHTML = "";
  5342.             dom.roomMockup.appendChild(img);
  5343.             // Reset all styling including background from fabric mode
  5344.             dom.roomMockup.style.cssText = "width: 600px; height: 450px; position: relative; background-image: none; background-color: var(--color-bg-medium);";
  5345.             ensureButtonsAfterUpdate();
  5346.  
  5347.            
  5348.             console.log("โœ… Furniture preview displayed in DOM");
  5349.             console.log("๐Ÿ“Š Final canvas dimensions:", canvas.width, "x", canvas.height);
  5350.             console.log("๐Ÿ“Š DataURL length:", dataUrl.length);
  5351.            
  5352.         } catch (renderError) {
  5353.             console.error("โŒ Error in furniture rendering sequence:", renderError);
  5354.             console.error("โŒ Error stack:", renderError.stack);
  5355.            
  5356.             // Fallback: show error message in mockup area
  5357.             dom.roomMockup.innerHTML = `
  5358.                 <div style="
  5359.                    width: 100%;
  5360.                    height: 100%;
  5361.                    display: flex;
  5362.                    align-items: center;
  5363.                    justify-content: center;
  5364.                    background: #f3f4f6;
  5365.                    color: #dc2626;
  5366.                    font-family: monospace;
  5367.                    text-align: center;
  5368.                    padding: 20px;
  5369.                ">
  5370.                     <div>
  5371.                         <div style="font-size: 24px; margin-bottom: 10px;">โš ๏ธ</div>
  5372.                         <div>Furniture Preview Error</div>
  5373.                         <div style="font-size: 12px; margin-top: 10px;">Check console for details</div>
  5374.                     </div>
  5375.                 </div>
  5376.             `;
  5377.         }
  5378.  
  5379.         // โœ… RESTORE PRESERVED SETTINGS AT THE END
  5380.         Object.assign(furnitureViewSettings, preservedSettings);
  5381.         console.log("โœ… Zoom settings restored after rendering:", furnitureViewSettings);
  5382.  
  5383.     } catch (mainError) {
  5384.         console.error("๐Ÿ”ฅ Critical error in updateFurniturePreview:", mainError);
  5385.         console.error("๐Ÿ”ฅ Error stack:", mainError.stack);
  5386.        
  5387.         // Ultimate fallback
  5388.         if (dom.roomMockup) {
  5389.             dom.roomMockup.innerHTML = `
  5390.                 <div style="
  5391.                    width: 100%;
  5392.                    height: 100%;
  5393.                    display: flex;
  5394.                    align-items: center;
  5395.                    justify-content: center;
  5396.                    background: #fef2f2;
  5397.                    color: #dc2626;
  5398.                    font-family: monospace;
  5399.                ">
  5400.                     Critical furniture preview error - check console
  5401.                 </div>
  5402.             `;
  5403.         }
  5404.     }
  5405. };
  5406.        
  5407.     function parseCoordinateFilename(filename) {
  5408.  
  5409.         console.log('Before click - Scroll Y:', window.scrollY);
  5410.  
  5411.  
  5412.         const parts = filename.split('/');
  5413.         const filePart = parts[5]; // "BOMBAY-KITANELLI-VINE.jpg"
  5414.         const collectionName = 'coordinates';
  5415.         const patternPart = filePart
  5416.             .replace(/^BOMBAY-/, '') // Remove "BOMBAY-"
  5417.             .replace(/\.jpg$/i, ''); // Remove ".jpg"
  5418.         const patternName = patternPart
  5419.             .split('-')
  5420.             .map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
  5421.             .join(' ');
  5422.         // No mapping needed to match JSON
  5423.         const normalizedPatternName = patternName;
  5424.         console.log(`Parsed filename: ${filename} รขโ€ โ€™ collection: ${collectionName}, pattern: ${normalizedPatternName}`);
  5425.         return { collectionName, patternName: normalizedPatternName };
  5426.     }
  5427.  
  5428.     function loadPatternFromLocalCollections(collectionName, patternName) {
  5429.         try {
  5430.             if (!appState.collections || !appState.collections.length) {
  5431.                 console.error("appState.collections is empty or not initialized");
  5432.                 return null;
  5433.             }
  5434.             const collection = appState.collections.find(
  5435.                 c => c.name.toLowerCase() === "coordinates"
  5436.             );
  5437.             if (!collection) {
  5438.                 console.error("Coordinates collection not found in appState.collections");
  5439.                 return null;
  5440.             }
  5441.             const pattern = collection.patterns.find(
  5442.                 p => p.name.toLowerCase() === patternName.toLowerCase()
  5443.             );
  5444.             if (!pattern) {
  5445.                 console.error(`Pattern ${patternName} not found in coordinates collection`);
  5446.                 return null;
  5447.             }
  5448.             console.log(`Loaded pattern: ${pattern.name} from coordinates collection`);
  5449.             return { collection, pattern };
  5450.         } catch (error) {
  5451.             console.error(`Error accessing collections: ${error.message}`);
  5452.             return null;
  5453.         }
  5454.     }
  5455.  
  5456.     function setupCoordinateImageHandlers() {
  5457.         const coordinateImages = document.querySelectorAll(".coordinate-image");
  5458.         console.log(`Found ${coordinateImages.length} coordinate images`);
  5459.         coordinateImages.forEach(image => {
  5460.             image.removeEventListener("click", handleCoordinateClick);
  5461.             image.addEventListener("click", handleCoordinateClick);
  5462.         });
  5463.    
  5464.         function handleCoordinateClick() {
  5465.             const image = this;
  5466.             console.log('>>> handleCoordinateClick START <<<');
  5467.        
  5468.             // Only store original state if not already stored
  5469.             if (!appState.originalPattern) {
  5470.                 appState.originalPattern = { ...appState.currentPattern };
  5471.                 appState.originalCoordinates = appState.selectedCollection?.coordinates ? [...appState.selectedCollection.coordinates] : [];
  5472.                 appState.originalLayerInputs = appState.layerInputs.map((layer, index) => ({
  5473.                     id: `layer-${index}`,
  5474.                     label: layer.label,
  5475.                     inputValue: layer.input.value,
  5476.                     hex: layer.circle.style.backgroundColor,
  5477.                     isBackground: layer.isBackground
  5478.                 }));
  5479.                 appState.originalCurrentLayers = appState.currentLayers.map(layer => ({ ...layer }));
  5480.                 console.log("Stored original state:", {
  5481.                     pattern: appState.originalPattern.name,
  5482.                     coordinates: appState.originalCoordinates,
  5483.                     layerInputs: appState.originalLayerInputs,
  5484.                     currentLayers: appState.originalCurrentLayers
  5485.                 });
  5486.             }
  5487.        
  5488.             // Highlight selected image
  5489.             document.querySelectorAll(".coordinate-image").forEach(img => img.classList.remove("selected"));
  5490.             image.classList.add("selected");
  5491.        
  5492.             const filename = image.dataset.filename;
  5493.             console.log(`Coordinate image clicked: ${filename}`);
  5494.        
  5495.             // Find the coordinate
  5496.             const coordinate = appState.selectedCollection?.coordinates?.find(coord => coord.path === filename);
  5497.             if (!coordinate) {
  5498.                 console.error(`Coordinate not found for filename: ${filename}`);
  5499.                 if (dom.coordinatesContainer) {
  5500.                     dom.coordinatesContainer.innerHTML += "<p style='color: red;'>Error: Coordinate not found.</p>";
  5501.                 }
  5502.                 return;
  5503.             }
  5504.             console.log(`Found coordinate:`, coordinate);
  5505.        
  5506.             // Find the primary pattern layer index (non-background, non-shadow)
  5507.             const primaryLayerIndex = appState.currentLayers.findIndex(layer =>
  5508.                 layer.label !== "Background" &&  
  5509.                 !layer.imageUrl?.toUpperCase().includes("ISSHADOW")
  5510.             );
  5511.             if (primaryLayerIndex === -1) {
  5512.                 console.error("No primary pattern layer found in appState.currentLayers:", appState.currentLayers);
  5513.                 return;
  5514.             }
  5515.             console.log(`Primary layer index: ${primaryLayerIndex}`);
  5516.        
  5517.             // Determine layers to use (handle both layerPath and layerPaths)
  5518.             const layerPaths = coordinate.layerPaths || (coordinate.layerPath ? [coordinate.layerPath] : []);
  5519.             if (layerPaths.length === 0) {
  5520.                 console.error(`No layers found for coordinate: ${filename}`);
  5521.                 return;
  5522.             }
  5523.        
  5524.             // Load the first coordinate image to get its dimensions
  5525.             const coordImage = new Image();
  5526.             const normalizedCoordPath = normalizePath(layerPaths[0]);
  5527.             console.log(`๐Ÿ” Coordinate click path: "${layerPaths[0]}" โ†’ normalized: "${normalizedCoordPath}"`);
  5528.             coordImage.src = normalizedCoordPath;
  5529.             coordImage.onload = () => {
  5530.                 // Limit coordinate image dimensions to prevent oversized canvases
  5531.                 const maxDimension = 400;
  5532.                 const naturalWidth = coordImage.naturalWidth;
  5533.                 const naturalHeight = coordImage.naturalHeight;
  5534.                 const scale = Math.min(maxDimension / naturalWidth, maxDimension / naturalHeight, 1);
  5535.                 const imageWidth = Math.floor(naturalWidth * scale);
  5536.                 const imageHeight = Math.floor(naturalHeight * scale);
  5537.                
  5538.                 console.log(`๐Ÿ“ Coordinate image sizing: natural(${naturalWidth}x${naturalHeight}) โ†’ scaled(${imageWidth}x${imageHeight})`);    
  5539.        
  5540.                 // Create layers and labels for all coordinate layers
  5541.                 const layers = layerPaths.map(path => ({ path }));
  5542.                 const layerLabels = layerPaths.map((_, index) => index === 0 ? "Flowers" : `Layer ${index + 1}`);
  5543.        
  5544.                 // Update currentPattern with coordinate data
  5545.                 appState.currentPattern = {
  5546.                     ...appState.currentPattern,
  5547.                     name: coordinate.filename.replace(/\.jpg$/, ''),
  5548.                     thumbnail: coordinate.path,
  5549.                     size: [imageWidth / 100, imageHeight / 100], // Convert pixels to inches (assuming 100 DPI)
  5550.                     layers: layers, // All coordinate layers
  5551.                     layerLabels: layerLabels,
  5552.                     tintWhite: false
  5553.                 };
  5554.                 console.log(`Updated appState.currentPattern:`, appState.currentPattern);
  5555.        
  5556.                 // Update the primary pattern layer's imageUrl in currentLayers
  5557.                 appState.currentLayers = appState.currentLayers.map((layer, index) => {
  5558.                     if (index === primaryLayerIndex) {
  5559.                         console.log(`Updating layer at index ${index} with layerPath: ${layerPaths[0]}`);
  5560.                         return {
  5561.                             ...layer,
  5562.                             imageUrl: layerPaths[0] // Update primary layer
  5563.                         };
  5564.                     }
  5565.                     return layer;
  5566.                 });
  5567.        
  5568.                 // Preserve the original layer structure and colors
  5569.                 const currentColors = appState.layerInputs.map(layer => layer.input.value);
  5570.                 console.log("Preserving colors:", currentColors);
  5571.        
  5572.                 // Restore layer inputs with preserved colors
  5573.                 appState.layerInputs = [];
  5574.                 if (dom.layerInputsContainer) dom.layerInputsContainer.innerHTML = "";
  5575.                 appState.currentLayers.forEach((layer, index) => {
  5576.                 const id = `layer-${index}`;
  5577.                 const isBackground = layer.label === "Background";
  5578.                 const initialColor = currentColors[index] || (isBackground ? "#FFFFFF" : "Snowbound");
  5579.                 const layerData = createColorInput(layer.label, id, initialColor, isBackground);
  5580.                 layerData.input.value = toInitialCaps(initialColor.replace(/^(SW|SC)\d+\s*/i, "").trim());
  5581.                 layerData.circle.style.backgroundColor = lookupColor(initialColor) || "#FFFFFF";
  5582.                
  5583.                 // โœ… ADD THIS LINE - append to DOM
  5584.                 dom.layerInputsContainer.appendChild(layerData.container);
  5585.                
  5586.                 appState.layerInputs[index] = layerData;
  5587.                 console.log(`Set ${layer.label} input to ${layerData.input.value}, circle to ${layerData.circle.style.backgroundColor}, id=${id}`);
  5588.             });
  5589.  
  5590.        
  5591.                 // Update UI
  5592.                 // updatePreview();
  5593.                 // const isFurniturePattern = appState.currentPattern?.isFurniture || false;
  5594.  
  5595.                
  5596.                 updatePreview();
  5597.                
  5598.                 // Check if we're in fabric mode - if so, only render fabric mockup
  5599.                 if (appState.isInFabricMode) {
  5600.                     console.log("๐Ÿงต handleCoordinateClick in fabric mode - calling renderFabricMockup()");
  5601.                     renderFabricMockup();
  5602.                 } else {
  5603.                     updateRoomMockup();
  5604.                 }
  5605.        
  5606.                 // Add "Back to Pattern" link
  5607.                 console.log("๐Ÿ” Adding Back to Pattern button...");
  5608.                 const coordinatesContainer = document.getElementById("coordinatesContainer");
  5609.                 console.log("๐Ÿ” coordinatesContainer found:", !!coordinatesContainer);
  5610.                 if (coordinatesContainer) {
  5611.                     let backLink = document.getElementById("backToPatternLink");
  5612.                     if (backLink) {
  5613.                         console.log("๐Ÿ” Removing existing back link");
  5614.                         backLink.remove();
  5615.                     }
  5616.                     backLink = document.createElement("div");
  5617.                     backLink.id = "backToPatternLink";
  5618.                     backLink.style.cssText = `
  5619.                         color: #f0e6d2 !important;
  5620.                         font-family: 'Island Moments', cursive !important;
  5621.                         font-size: 1.8rem !important;
  5622.                         text-align: center !important;
  5623.                         cursor: pointer !important;
  5624.                         margin-top: 6rem !important;
  5625.                         padding: 0.5rem !important;
  5626.                         transition: color 0.2s !important;
  5627.                         display: block !important;
  5628.                         visibility: visible !important;
  5629.                         opacity: 1 !important;
  5630.                         z-index: 1000 !important;
  5631.                         position: relative !important;
  5632.                     `;
  5633.                     backLink.textContent = "  โ† Back to Pattern ";
  5634.                     backLink.addEventListener("mouseover", () => {
  5635.                         backLink.style.color = "#beac9f";
  5636.                     });
  5637.                     backLink.addEventListener("mouseout", () => {
  5638.                         backLink.style.color = "#f0e6d2";
  5639.                     });
  5640.                     coordinatesContainer.appendChild(backLink);
  5641.                     backLink.addEventListener("click", restoreOriginalPattern);
  5642.                     console.log("โœ… Back to Pattern button added successfully");
  5643.                 } else {
  5644.                     console.error("โŒ coordinatesContainer not found - cannot add back link");
  5645.                 }
  5646.             };
  5647.             coordImage.onerror = () => {
  5648.                 console.error(`Failed to load coordinate image: ${layerPaths[0] || coordinate.layerPath}`);
  5649.             };
  5650.         }
  5651.     }
  5652.  
  5653.     function restoreOriginalPattern() {
  5654.     try {
  5655.         console.log('>>> restoreOriginalPattern START <<<');
  5656.  
  5657.         if (!appState.originalPattern || !appState.originalCurrentLayers || !appState.originalLayerInputs) {
  5658.             console.warn("No original state to restore", {
  5659.                 originalPattern: appState.originalPattern,
  5660.                 originalCurrentLayers: appState.originalCurrentLayers,
  5661.                 originalLayerInputs: appState.originalLayerInputs
  5662.             });
  5663.             return;
  5664.         }
  5665.         console.log("Restoring original pattern:", appState.originalPattern.name,
  5666.                     "Original state:", {
  5667.                         layerInputs: appState.originalLayerInputs,
  5668.                         currentLayers: appState.originalCurrentLayers
  5669.                     });
  5670.  
  5671.         // Restore appState to the original pattern
  5672.         appState.currentPattern = { ...appState.originalPattern };
  5673.         appState.currentLayers = appState.originalCurrentLayers.map(layer => ({ ...layer }));
  5674.         console.log("Restored appState: collection=", appState.selectedCollection.name,
  5675.                     "pattern=", appState.currentPattern.name);
  5676.  
  5677.         // Restore layer inputs
  5678.  
  5679.         appState.originalLayerInputs.forEach((layer, index) => {
  5680.             const id = layer.id || `layer-${index}`;
  5681.             const layerData = createColorInput(layer.label, id, layer.inputValue, layer.isBackground);
  5682.             layerData.input.value = toInitialCaps(layer.inputValue.replace(/^(SW|SC)\d+\s*/i, "").trim());
  5683.             layerData.circle.style.backgroundColor = layer.hex;
  5684.             appState.layerInputs[index] = layerData;
  5685.             console.log(`Restored ${layer.label} input to ${layer.inputValue}, circle to ${layer.hex}, id=${id}`);
  5686.         });
  5687.  
  5688.         console.log("After restore, layerInputs:",
  5689.                     appState.layerInputs.map(l => ({ id: l.input.id, label: l.label, value: l.input.value })));
  5690.  
  5691.         // Update UI      
  5692.         updatePreview();
  5693.        
  5694.         // Check if we're in fabric mode - if so, only render fabric mockup
  5695.         if (appState.isInFabricMode) {
  5696.             console.log("๐Ÿงต restoreOriginalPattern in fabric mode - calling renderFabricMockup()");
  5697.             renderFabricMockup();
  5698.         } else {
  5699.             updateRoomMockup();
  5700.         }
  5701.        
  5702.         populateCoordinates();
  5703.  
  5704.         // Remove Back to Pattern link and clean up
  5705.         const coordinatesSection = document.getElementById("coordinatesSection");
  5706.         const backLink = document.getElementById("backToPatternLink");
  5707.         if (backLink) {
  5708.             backLink.remove();
  5709.             console.log("Removed Back to Pattern link");
  5710.         }
  5711.         const errorMessages = coordinatesSection.querySelectorAll("p[style*='color: red']");
  5712.         errorMessages.forEach(msg => msg.remove());
  5713.         console.log("Cleared error messages:", errorMessages.length);
  5714.  
  5715.         console.log('>>> restoreOriginalPattern END <<<');
  5716.     } catch (e) {
  5717.         console.error("Error restoring original pattern:", e);
  5718.     }
  5719. }
  5720.  
  5721. // Update displays with layer compositing
  5722. function updateDisplays() {
  5723.     try {
  5724.         console.log('updateDisplays called');
  5725.        
  5726.         // โœ… Always update pattern preview
  5727.         updatePreview();
  5728.        
  5729.         // Check if we're in fabric mode - if so, only render fabric mockup
  5730.         if (appState.isInFabricMode) {
  5731.             console.log("๐Ÿงต updateDisplays in fabric mode - calling renderFabricMockup()");
  5732.             renderFabricMockup();
  5733.         } else {
  5734.             updateRoomMockup();
  5735.         }
  5736.         populateCoordinates();
  5737.     } catch (e) {
  5738.         console.error('Error in updateDisplays:', e);
  5739.     }
  5740. }
  5741.  
  5742. function handleThumbnailClick(patternId) {
  5743.     console.log(`handleThumbnailClick: patternId=${patternId}`);
  5744.     if (!patternId) {
  5745.         console.error("Invalid pattern ID:", patternId);
  5746.         return;
  5747.     }
  5748.    
  5749.     try {
  5750.         // Preserve current mockup
  5751.         const originalMockup = appState.selectedCollection?.mockup || "";
  5752.         console.log("Preserving mockup for thumbnail click:", originalMockup);
  5753.  
  5754.         loadPatternData(appState.selectedCollection, patternId);
  5755.  
  5756.         // Update thumbnails
  5757.         document.querySelectorAll(".thumbnail").forEach(t => t.classList.remove("selected"));
  5758.         const selectedThumb = document.querySelector(`.thumbnail[data-pattern-id="${patternId}"]`);
  5759.         if (selectedThumb) {
  5760.             selectedThumb.classList.add("selected");
  5761.             console.log(`Selected thumbnail: ${patternId}`);
  5762.         } else {
  5763.             console.warn(`Thumbnail not found for ID: ${patternId}`);
  5764.         }
  5765.     } catch (error) {
  5766.         console.error("Error handling thumbnail click:", error);
  5767.     }
  5768. }
  5769.  
  5770. // Generate print preview
  5771. const generatePrintPreview = () => {
  5772.     if (!appState.currentPattern) {
  5773.         console.error("No current pattern selected for print preview");
  5774.         return null;
  5775.     }
  5776.  
  5777.     const isWall = appState.currentPattern?.isWall || appState.selectedCollection?.name === "wall-panels";
  5778.     const backgroundIndex = isWall ? 1 : 0;
  5779.     const backgroundInput = appState.layerInputs[backgroundIndex]?.input;
  5780.     if (!backgroundInput) {
  5781.         console.error(`Background input not found at index ${backgroundIndex}`, appState.layerInputs);
  5782.         return null;
  5783.     }
  5784.  
  5785.     const backgroundColor = lookupColor(backgroundInput.value);
  5786.     console.log("Print preview - Background color:", backgroundColor, "isWall:", isWall);
  5787.     console.log("Print preview - Layer inputs:", appState.layerInputs.map((li, i) => ({
  5788.         index: i,
  5789.         value: li?.input?.value
  5790.     })));
  5791.  
  5792.     const dpi = 100;
  5793.     const patternWidthInches = appState.currentPattern?.size?.[0] || 24;
  5794.     const patternHeightInches = appState.currentPattern?.size?.[1] || 24;
  5795.     const printWidth = Math.round(patternWidthInches * dpi);
  5796.     const printHeight = Math.round(patternHeightInches * dpi);
  5797.     const aspectRatio = patternHeightInches / patternWidthInches;
  5798.  
  5799.     console.log(`Print preview - Pattern: ${patternWidthInches}x${patternHeightInches}, Aspect: ${aspectRatio}`);
  5800.     console.log(`Print canvas: ${printWidth}x${printHeight}, DPI: ${dpi}`);
  5801.  
  5802.     const printCanvas = document.createElement("canvas");
  5803.     const printCtx = printCanvas.getContext("2d", { willReadFrequently: true });
  5804.     printCanvas.width = printWidth;
  5805.     printCanvas.height = printHeight;
  5806.  
  5807.     const collectionName = toInitialCaps(appState.selectedCollection?.name || "Unknown");
  5808.     const patternName = toInitialCaps(appState.currentPattern.name || "Pattern");
  5809.     let layerLabels = [];
  5810.  
  5811.     const processPrintPreview = async () => {
  5812.         printCtx.fillStyle = backgroundColor;
  5813.         printCtx.fillRect(0, 0, printWidth, printHeight);
  5814.         console.log("Print preview - Filled background with:", backgroundColor);
  5815.  
  5816.         const isTintWhite = appState.currentPattern?.tintWhite || false;
  5817.         console.log(`Print preview - tintWhite flag: ${isTintWhite}`);
  5818.  
  5819.         if (isTintWhite && appState.currentPattern?.baseComposite) {        } else if (appState.currentPattern?.layers?.length) {
  5820.             layerLabels = appState.currentPattern.layers.map((l, i) => ({
  5821.                 label: appState.currentPattern.layerLabels?.[i] || `Layer ${i + 1}`,
  5822.                 color: appState.layerInputs[i + (isWall ? 2 : 1)]?.input?.value || "Snowbound"
  5823.             }));
  5824.            
  5825.             // Add background color to the beginning of the color list
  5826.             layerLabels.unshift({
  5827.                 label: "Background",
  5828.                 color: backgroundInput.value || "Snowbound"
  5829.             });
  5830.  
  5831.             const shadowLayers = [];
  5832.             const nonShadowLayers = [];
  5833.             appState.currentPattern.layers.forEach((layer, index) => {
  5834.                 const label = layerLabels[index].label;
  5835.                 const isShadow = layer.isShadow === true;
  5836.                 (isShadow ? shadowLayers : nonShadowLayers).push({ layer, index, label });
  5837.             });
  5838.  
  5839.             let nonShadowInputIndex = isWall ? 2 : 1;
  5840.  
  5841.             for (const { layer, index, label } of shadowLayers) {
  5842.                 const layerPath = layer.path || "";
  5843.                 await new Promise((resolve) => {
  5844.                     processImage(
  5845.                         layerPath,
  5846.                         (processedUrl) => {
  5847.                             const img = new Image();
  5848.                             console.log("๐Ÿงช processedUrl type:", typeof processedUrl, processedUrl);
  5849.                             if (processedUrl instanceof HTMLCanvasElement) {
  5850.                                 img.src = processedUrl.toDataURL("image/png");
  5851.                             } else {
  5852.                                 img.src = processedUrl;
  5853.                             }
  5854.                             img.onload = () => {
  5855.                                 printCtx.globalCompositeOperation = "multiply";
  5856.                                 printCtx.globalAlpha = 0.3;
  5857.                                 printCtx.drawImage(img, 0, 0, printWidth, printHeight);
  5858.                                 resolve();
  5859.                             };
  5860.                             img.onerror = () => resolve();
  5861.                         },
  5862.                         null,
  5863.                         2.2,
  5864.                         true,
  5865.                         isWall
  5866.                     );
  5867.                 });
  5868.             }
  5869.  
  5870.             for (const { layer, index, label } of nonShadowLayers) {
  5871.                 const layerPath = layer.path || "";
  5872.                 const layerInput = appState.layerInputs[nonShadowInputIndex];
  5873.                 const layerColor = lookupColor(layerInput?.input?.value || "Snowbound");
  5874.                 await new Promise((resolve) => {
  5875.                     processImage(
  5876.                         layerPath,
  5877.                         (processedUrl) => {
  5878.                             const img = new Image();
  5879.                             console.log("๐Ÿงช processedUrl type:", typeof processedUrl, processedUrl);
  5880.                             if (processedUrl instanceof HTMLCanvasElement) {
  5881.                                 img.src = processedUrl.toDataURL("image/png");
  5882.                             } else {
  5883.                                 img.src = processedUrl;
  5884.                             }
  5885.                             img.onload = () => {
  5886.                                 printCtx.globalCompositeOperation = "source-over";
  5887.                                 printCtx.globalAlpha = 1.0;
  5888.                                 printCtx.drawImage(img, 0, 0, printWidth, printHeight);
  5889.                                 nonShadowInputIndex++;
  5890.                                 resolve();
  5891.                             };
  5892.                             img.onerror = () => resolve();
  5893.                         },
  5894.                         layerColor,
  5895.                         2.2,
  5896.                         false,
  5897.                         isWall
  5898.                     );
  5899.                 });
  5900.             }
  5901.         }
  5902.  
  5903.         const dataUrl = printCanvas.toDataURL("image/png");
  5904.         console.log(`Print preview - Generated data URL, length: ${dataUrl.length}`);
  5905.  
  5906.         // Generate HTML content
  5907.         let textContent = `
  5908.             <img src="https://so-animation.com/colorflex/img/SC-header-mage.jpg" alt="SC Logo" class="sc-logo">
  5909.             <h2>${collectionName}</h2>
  5910.             <h3>${patternName}</h3>
  5911.             <ul style="list-style: none; padding: 0;">
  5912.         `;
  5913.         layerLabels.forEach(({ label, color }, index) => {
  5914.             const swNumber = appState.selectedCollection?.curatedColors?.[index] || color || "N/A";
  5915.             textContent += `
  5916.                 <li>${toInitialCaps(label)} | ${swNumber}</li>
  5917.             `;
  5918.         });
  5919.         textContent += "</ul>";
  5920.  
  5921.         // Open preview window
  5922.         const previewWindow = window.open('', '_blank', 'width=800,height=1200');
  5923.         if (!previewWindow) {
  5924.             console.error("Print preview - Failed to open preview window");
  5925.             return { canvas: printCanvas, dataUrl };
  5926.         }
  5927.  
  5928.         previewWindow.document.write(`
  5929.             <html>
  5930.                 <head>
  5931.                     <title>Print Preview</title>
  5932.                     <link href="https://fonts.googleapis.com/css2?family=Special+Elite&display=swap" rel="stylesheet">
  5933.                     <style>
  5934.                         body {
  5935.                             font-family: 'Special Elite', 'Times New Roman', serif !important;
  5936.                             padding: 20px;
  5937.                             margin: 0;
  5938.                             display: flex;
  5939.                             justify-content: center;
  5940.                             align-items: flex-start;
  5941.                             min-height: 100vh;
  5942.                             background-color: #111827;
  5943.                             color: #f0e6d2;
  5944.                             overflow: auto;
  5945.                         }
  5946.                         .print-container {
  5947.                             text-align: center;
  5948.                             max-width: 600px;
  5949.                             width: 100%;
  5950.                             display: flex;
  5951.                             flex-direction: column;
  5952.                             align-items: center;
  5953.                             background-color: #434341;
  5954.                             padding: 20px;
  5955.                             border-radius: 8px;
  5956.                         }
  5957.                         .sc-logo {
  5958.                             width: 400px !important;
  5959.                             height: auto;
  5960.                             margin: 0 auto 20px;
  5961.                             display: block;
  5962.                         }
  5963.                         h2 { font-size: 24px; margin: 10px 0; }
  5964.                         h3 { font-size: 20px; margin: 5px 0; }
  5965.                         ul { margin: 10px 0; }
  5966.                         li { margin: 5px 0; font-size: 16px; }
  5967.                         img { max-width: 100%; height: auto; margin: 20px auto; display: block; }
  5968.                         .button-container { margin-top: 20px; }
  5969.                         button {
  5970.                             font-family: 'Special Elite', serif;
  5971.                             padding: 10px 20px;
  5972.                             margin: 0 10px;
  5973.                             font-size: 16px;
  5974.                             cursor: pointer;
  5975.                             background-color: #f0e6d2;
  5976.                             color: #111827;
  5977.                             border: none;
  5978.                             border-radius: 4px;
  5979.                         }
  5980.                         button:hover {
  5981.                             background-color: #e0d6c2;
  5982.                         }
  5983.                     </style>
  5984.                 </head>
  5985.                 <body>
  5986.                     <div class="print-container">
  5987.                         ${textContent}
  5988.                         <img src="${dataUrl}" alt="Pattern Preview">
  5989.                         <div class="button-container">
  5990.                             <button onclick="window.print();">Print</button>
  5991.                             <button onclick="download()">Download</button>
  5992.                             <button onclick="window.close();">Close</button>
  5993.                         </div>
  5994.                     </div>
  5995.                     <script>
  5996.                         function download() {
  5997.                             const link = document.createElement("a");
  5998.                             link.href = "${dataUrl}";
  5999.                             link.download = "${patternName}-print.png";
  6000.                             link.click();
  6001.                         }
  6002.                     </script>
  6003.                 </body>
  6004.             </html>
  6005.         `);
  6006.         previewWindow.document.close();
  6007.         console.log("Print preview - Preview window opened");
  6008.  
  6009.         return { canvas: printCanvas, dataUrl, layerLabels, collectionName, patternName };
  6010.     };
  6011.  
  6012.     return processPrintPreview().catch(error => {
  6013.         console.error("Print preview error:", error);
  6014.         return null;
  6015.     });
  6016. };
  6017.  
  6018. // Start the app
  6019. async function startApp() {
  6020.     await initializeApp();
  6021.     // Call this when app starts
  6022.     await loadFurnitureConfig();
  6023.  
  6024.     isAppReady = true;
  6025.  
  6026.     console.log("โœ… App fully initialized and ready.");
  6027. }
  6028.  
  6029. // Run immediately if DOM is already ready
  6030. if (document.readyState === "loading") {
  6031.     document.addEventListener("DOMContentLoaded", startApp);
  6032. } else {
  6033.     startApp();
  6034. }
  6035.  
  6036. // === PATTERN TYPE HELPERS ===
  6037.  
  6038. function getPatternType(pattern, collection) {
  6039.     if (collection?.name === "wall-panels") return "wall-panel";
  6040.     if (pattern?.tintWhite) return "tint-white";
  6041.     if (collection?.elements?.length) return "element-coloring";
  6042.     return "standard";
  6043. }
  6044.  
  6045. function getColorMapping(patternType, currentLayers, layerIndex) {
  6046.     switch (patternType) {
  6047.         case "wall-panel":
  6048.             return currentLayers[layerIndex + 2]; // Skip wall + background
  6049.         case "standard":
  6050.             const inputLayers = currentLayers.filter(layer => !layer.isShadow);
  6051.             return inputLayers[layerIndex + 1]; // Skip background
  6052.         case "element-coloring":
  6053.             // Future: element-specific color mapping
  6054.             const inputLayersElement = currentLayers.filter(layer => !layer.isShadow);
  6055.             return inputLayersElement[layerIndex + 1];
  6056.         default:
  6057.             return currentLayers[layerIndex + 1];
  6058.     }
  6059. }
  6060.  
  6061.  
  6062.  
  6063. // Add fabric tuning controls
  6064. function addFabricTuningControls() {
  6065.     // Check if controls should be shown
  6066.     if (!SHOW_FABRIC_CONTROLS) {
  6067.         return; // Exit early if controls are disabled
  6068.     }
  6069.    
  6070.     // Remove existing controls
  6071.     const existingControls = document.getElementById('fabricTuningControls');
  6072.     if (existingControls) {
  6073.         existingControls.remove();
  6074.     }
  6075.    
  6076.     // Create control panel
  6077.     const controlPanel = document.createElement('div');
  6078.     controlPanel.id = 'fabricTuningControls';
  6079.     controlPanel.style.cssText = `
  6080.         position: fixed;
  6081.         top: 20px;
  6082.         right: 20px;
  6083.         background: rgba(0, 0, 0, 0.9);
  6084.         color: white;
  6085.         padding: 15px;
  6086.         border-radius: 8px;
  6087.         border: 2px solid #d4af37;
  6088.         z-index: 1000;
  6089.         font-family: monospace;
  6090.         font-size: 12px;
  6091.         max-width: 300px;
  6092.         box-shadow: 0 4px 12px rgba(0, 0, 0, 0.5);
  6093.     `;
  6094.    
  6095.     // Add title
  6096.     const title = document.createElement('h3');
  6097.     title.textContent = '๐Ÿงต Fabric Tuning';
  6098.     title.style.cssText = 'margin: 0 0 10px 0; color: #d4af37; font-size: 14px;';
  6099.     controlPanel.appendChild(title);
  6100.    
  6101.     // Create sliders for each parameter
  6102.     const params = [
  6103.         { key: 'alphaStrength', label: 'Pattern Opacity', min: 0, max: 2, step: 0.1 },
  6104.         { key: 'baseTintStrength', label: 'Base Color Tint', min: 0, max: 2, step: 0.1 },
  6105.         { key: 'patternContrast', label: 'Pattern Contrast', min: 0.1, max: 3, step: 0.1 },
  6106.         { key: 'shadowMultiplier', label: 'Shadow Interaction', min: 0, max: 2, step: 0.1 },
  6107.         { key: 'colorVibrance', label: 'Color Vibrance', min: 0, max: 2, step: 0.1 },
  6108.         { key: 'glossyStrength', label: 'Glossy Finish', min: 0, max: 2, step: 0.1 }
  6109.     ];
  6110.    
  6111.     // Add blend mode selector
  6112.     const blendModeContainer = document.createElement('div');
  6113.     blendModeContainer.style.cssText = 'margin-bottom: 10px;';
  6114.    
  6115.     const blendModeLabel = document.createElement('label');
  6116.     blendModeLabel.textContent = 'Blend Mode';
  6117.     blendModeLabel.style.cssText = 'display: block; margin-bottom: 3px; font-weight: bold;';
  6118.    
  6119.     const blendModeSelect = document.createElement('select');
  6120.     blendModeSelect.style.cssText = 'width: 100%; padding: 2px; background: #333; color: white; border: 1px solid #555;';
  6121.    
  6122.     const blendModes = [
  6123.         { value: 'auto', label: 'Auto (Smart)' },
  6124.         { value: 'multiply', label: 'Multiply' },
  6125.         { value: 'overlay', label: 'Overlay' },
  6126.         { value: 'soft-light', label: 'Soft Light' },
  6127.         { value: 'hard-light', label: 'Hard Light' },
  6128.         { value: 'screen', label: 'Screen' }
  6129.     ];
  6130.    
  6131.     blendModes.forEach(mode => {
  6132.         const option = document.createElement('option');
  6133.         option.value = mode.value;
  6134.         option.textContent = mode.label;
  6135.         if (mode.value === fabricTuning.blendMode) {
  6136.             option.selected = true;
  6137.         }
  6138.         blendModeSelect.appendChild(option);
  6139.     });
  6140.    
  6141.     blendModeSelect.addEventListener('change', (e) => {
  6142.         fabricTuning.blendMode = e.target.value;
  6143.         debouncedFabricRender();
  6144.     });
  6145.    
  6146.     blendModeContainer.appendChild(blendModeLabel);
  6147.     blendModeContainer.appendChild(blendModeSelect);
  6148.     controlPanel.appendChild(blendModeContainer);
  6149.    
  6150.     params.forEach(param => {
  6151.         const container = document.createElement('div');
  6152.         container.style.cssText = 'margin-bottom: 10px;';
  6153.        
  6154.         const label = document.createElement('label');
  6155.         label.textContent = param.label;
  6156.         label.style.cssText = 'display: block; margin-bottom: 3px; font-weight: bold;';
  6157.        
  6158.         const slider = document.createElement('input');
  6159.         slider.type = 'range';
  6160.         slider.min = param.min;
  6161.         slider.max = param.max;
  6162.         slider.step = param.step;
  6163.         slider.value = fabricTuning[param.key];
  6164.         slider.style.cssText = 'width: 100%; margin-bottom: 2px;';
  6165.        
  6166.         const valueDisplay = document.createElement('span');
  6167.         valueDisplay.textContent = fabricTuning[param.key].toFixed(1);
  6168.         valueDisplay.style.cssText = 'color: #d4af37; font-weight: bold;';
  6169.        
  6170.         // Update function
  6171.         slider.addEventListener('input', (e) => {
  6172.             const value = parseFloat(e.target.value);
  6173.             fabricTuning[param.key] = value;
  6174.             valueDisplay.textContent = value.toFixed(1);
  6175.            
  6176.             // Re-render fabric in real-time with debounce
  6177.             debouncedFabricRender();
  6178.         });
  6179.        
  6180.         container.appendChild(label);
  6181.         container.appendChild(slider);
  6182.         container.appendChild(valueDisplay);
  6183.         controlPanel.appendChild(container);
  6184.     });
  6185.    
  6186.     // Add reset button
  6187.     const resetBtn = document.createElement('button');
  6188.     resetBtn.textContent = 'Reset to Defaults';
  6189.     resetBtn.style.cssText = `
  6190.         background: #d4af37;
  6191.         color: black;
  6192.         border: none;
  6193.         padding: 5px 10px;
  6194.         border-radius: 4px;
  6195.         cursor: pointer;
  6196.         font-size: 11px;
  6197.         font-weight: bold;
  6198.         margin-top: 10px;
  6199.         width: 100%;
  6200.     `;
  6201.    
  6202.     resetBtn.addEventListener('click', () => {
  6203.         fabricTuning.alphaStrength = 1.0;
  6204.         fabricTuning.baseTintStrength = 1.0;
  6205.         fabricTuning.patternContrast = 1.0;
  6206.         fabricTuning.shadowMultiplier = 1.0;
  6207.         fabricTuning.colorVibrance = 1.2;
  6208.         fabricTuning.blendMode = 'auto';
  6209.         fabricTuning.glossyStrength = 1.0;
  6210.        
  6211.         // Update slider values
  6212.         controlPanel.querySelectorAll('input[type="range"]').forEach((slider, index) => {
  6213.             slider.value = Object.values(fabricTuning)[index];
  6214.         });
  6215.         controlPanel.querySelectorAll('span').forEach((span, index) => {
  6216.             if (index < 5) { // Only update value displays
  6217.                 span.textContent = Object.values(fabricTuning)[index].toFixed(1);
  6218.             }
  6219.         });
  6220.        
  6221.         // Update blend mode selector
  6222.         const blendModeSelect = controlPanel.querySelector('select');
  6223.         if (blendModeSelect) {
  6224.             blendModeSelect.value = fabricTuning.blendMode;
  6225.         }
  6226.        
  6227.         // Re-render with debounce
  6228.         debouncedFabricRender();
  6229.     });
  6230.    
  6231.     controlPanel.appendChild(resetBtn);
  6232.    
  6233.     // Add copy values button
  6234.     const copyBtn = document.createElement('button');
  6235.     copyBtn.textContent = 'Copy Values to Console';
  6236.     copyBtn.style.cssText = `
  6237.         background: #4a5568;
  6238.         color: white;
  6239.         border: none;
  6240.         padding: 5px 10px;
  6241.         border-radius: 4px;
  6242.         cursor: pointer;
  6243.         font-size: 11px;
  6244.         font-weight: bold;
  6245.         margin-top: 5px;
  6246.         width: 100%;
  6247.     `;
  6248.    
  6249.     copyBtn.addEventListener('click', () => {
  6250.         console.log('๐Ÿงต Current fabric tuning values:');
  6251.         console.log('fabricTuning = {');
  6252.         Object.entries(fabricTuning).forEach(([key, value]) => {
  6253.             console.log(`    ${key}: ${value},`);
  6254.         });
  6255.         console.log('};');
  6256.     });
  6257.    
  6258.     controlPanel.appendChild(copyBtn);
  6259.    
  6260.     // Add to document
  6261.     document.body.appendChild(controlPanel);
  6262. }
  6263.  
  6264. // Function to remove fabric tuning controls
  6265. function removeFabricTuningControls() {
  6266.     const existingControls = document.getElementById('fabricTuningControls');
  6267.     if (existingControls) {
  6268.         existingControls.remove();
  6269.     }
  6270. }
  6271.  
  6272. // Simple fabric mockup function
  6273. async function renderFabricMockup() {
  6274.     console.log("๐Ÿงต ================================");
  6275.     console.log("๐Ÿงต FABRIC MOCKUP STARTING");
  6276.     console.log("๐Ÿงต ================================");
  6277.    
  6278.     const canvas = document.createElement("canvas");
  6279.     const ctx = canvas.getContext("2d");
  6280.    
  6281.     // Will be dynamically sized based on first loaded image
  6282.     let canvasWidth = 600;  // Default fallback
  6283.     let canvasHeight = 450; // Default fallback
  6284.    
  6285.     // Get fabric config with error handling
  6286.     console.log("๐Ÿ” Global furnitureConfig:", furnitureConfig);
  6287.     console.log("๐Ÿ” Collection furnitureConfig:", appState.selectedCollection?.furnitureConfig);
  6288.    
  6289.     // Try to get furniture config from collection first, then fall back to global
  6290.     let actualFurnitureConfig = appState.selectedCollection?.furnitureConfig || furnitureConfig;
  6291.     console.log("๐Ÿ” Using furnitureConfig:", actualFurnitureConfig);
  6292.    
  6293.     const fabricConfig = actualFurnitureConfig?.fabric;
  6294.    
  6295.     if (!fabricConfig) {
  6296.         console.error("โŒ Fabric config not found in furnitureConfig!");
  6297.         console.log("๐Ÿ” Available furniture config keys:", Object.keys(actualFurnitureConfig || {}));
  6298.         return;
  6299.     }
  6300.    
  6301.     console.log("๐Ÿ” Fabric config:", fabricConfig);
  6302.    
  6303.     // Get background color (first layer is Background)
  6304.     console.log("๐Ÿ” Current layers:", appState.currentLayers);
  6305.     console.log("๐Ÿ” First layer:", appState.currentLayers[0]);
  6306.     const backgroundColor = lookupColor(appState.currentLayers[0]?.color || "Snowbound");
  6307.     console.log("๐ŸŽจ Background color:", backgroundColor);
  6308.     console.log("๐Ÿ” Base tint strength:", fabricTuning.baseTintStrength);
  6309.    
  6310.     try {
  6311.         // 1. Load and draw room mockup background
  6312.         const mockupBg = new Image();
  6313.         mockupBg.crossOrigin = "anonymous";
  6314.        
  6315.         await new Promise((resolve, reject) => {
  6316.             mockupBg.onload = resolve;
  6317.             mockupBg.onerror = reject;
  6318.             mockupBg.src = `https://so-animation.com/colorflex/${fabricConfig.mockup}`;
  6319.         });
  6320.        
  6321.         // Set canvas size based on mockup image dimensions
  6322.         canvasWidth = mockupBg.width;
  6323.         canvasHeight = mockupBg.height;
  6324.         canvas.width = canvasWidth;
  6325.         canvas.height = canvasHeight;
  6326.        
  6327.         console.log(`๐Ÿ“ Canvas sized to match mockup: ${canvasWidth}x${canvasHeight}`);
  6328.        
  6329.         // Draw room background at full resolution
  6330.         ctx.drawImage(mockupBg, 0, 0);
  6331.        
  6332.         // 2. Load fabric base for later use
  6333.         const fabricBase = new Image();
  6334.         fabricBase.crossOrigin = "anonymous";
  6335.        
  6336.         await new Promise((resolve, reject) => {
  6337.             fabricBase.onload = resolve;
  6338.             fabricBase.onerror = reject;
  6339.             fabricBase.src = `https://so-animation.com/colorflex/${fabricConfig.base}`;
  6340.         });
  6341.        
  6342.         console.log(`๐Ÿ“ Fabric base: ${fabricBase.width}x${fabricBase.height}`);
  6343.        
  6344.         // 3. Create tinted base layer using fabric base alpha channel
  6345.         const baseCanvas = document.createElement("canvas");
  6346.         const baseCtx = baseCanvas.getContext("2d");
  6347.         baseCanvas.width = canvasWidth;
  6348.         baseCanvas.height = canvasHeight;
  6349.        
  6350.         // Draw fabric base to get alpha channel at full resolution
  6351.         baseCtx.drawImage(fabricBase, 0, 0, canvasWidth, canvasHeight);
  6352.        
  6353.         // Extract alpha channel and apply background color tint
  6354.         const baseImageData = baseCtx.getImageData(0, 0, canvasWidth, canvasHeight);
  6355.         const baseData = baseImageData.data;
  6356.        
  6357.         // Parse background color
  6358.         const bgColorMatch = backgroundColor.match(/^#([0-9a-f]{6})$/i);
  6359.         if (bgColorMatch) {
  6360.             const bgR = parseInt(bgColorMatch[1].substr(0, 2), 16);
  6361.             const bgG = parseInt(bgColorMatch[1].substr(2, 2), 16);
  6362.             const bgB = parseInt(bgColorMatch[1].substr(4, 2), 16);
  6363.            
  6364.             for (let j = 0; j < baseData.length; j += 4) {
  6365.                 const r = baseData[j];
  6366.                 const g = baseData[j + 1];
  6367.                 const b = baseData[j + 2];
  6368.                 const alpha = baseData[j + 3];
  6369.                
  6370.                 if (alpha > 0) {
  6371.                     const tintStrength = fabricTuning.baseTintStrength;
  6372.                    
  6373.                     // Apply background color tint
  6374.                     baseData[j] = Math.floor(bgR * tintStrength + r * (1 - tintStrength));
  6375.                     baseData[j + 1] = Math.floor(bgG * tintStrength + g * (1 - tintStrength));
  6376.                     baseData[j + 2] = Math.floor(bgB * tintStrength + b * (1 - tintStrength));
  6377.                     // Keep original alpha channel
  6378.                 }
  6379.             }
  6380.            
  6381.             baseCtx.putImageData(baseImageData, 0, 0);
  6382.         }
  6383.        
  6384.         console.log("โœ… Created tinted base layer with fabric alpha channel");
  6385.        
  6386.         // Load pattern layers using the fabric config from furniture-config.json
  6387.         const patternSlug = createPatternSlug(appState.currentPattern.name);
  6388.         const pattern = appState.currentPattern;
  6389.        
  6390.         console.log(`๐Ÿ” Pattern layers available:`, pattern.layers);
  6391.         console.log(`๐Ÿ” Fabric config patternPathTemplate:`, fabricConfig.patternPathTemplate);
  6392.        
  6393.         // Process pattern layers (skip Background layer at index 0)
  6394.         for (let i = 0; i < pattern.layers.length; i++) {
  6395.             const layer = pattern.layers[i];
  6396.             console.log(`๐Ÿ” Pattern layer ${i} object:`, layer);
  6397.            
  6398.             // Extract filename from layer's path or imageUrl and change extension to .png
  6399.             let layerFileName;
  6400.             if (typeof layer === 'string') {
  6401.                 layerFileName = layer;
  6402.             } else if (layer.path) {
  6403.                 const originalFileName = layer.path.split('/').pop();
  6404.                 layerFileName = originalFileName.replace(/\.(jpg|jpeg)$/i, '.png');
  6405.             } else if (layer.imageUrl) {
  6406.                 const originalFileName = layer.imageUrl.split('/').pop();
  6407.                 layerFileName = originalFileName.replace(/\.(jpg|jpeg)$/i, '.png');
  6408.             } else {
  6409.                 layerFileName = `${patternSlug}_layer-${i+1}.png`;
  6410.             }
  6411.            
  6412.             // Use the patternPathTemplate from fabric config
  6413.             const layerPath = `https://so-animation.com/colorflex/${fabricConfig.patternPathTemplate
  6414.                 .replace('{collection}', appState.selectedCollection.name)
  6415.                 .replace('{patternSlug}', patternSlug)}${layerFileName}`;
  6416.            
  6417.             console.log(`๐Ÿ” Loading pattern layer ${i}: ${layerPath}`);
  6418.            
  6419.             try {
  6420.                 const layerImg = new Image();
  6421.                 layerImg.crossOrigin = "anonymous";
  6422.                
  6423.                 await new Promise((resolve, reject) => {
  6424.                     layerImg.onload = resolve;
  6425.                     layerImg.onerror = reject;
  6426.                     layerImg.src = layerPath;
  6427.                 });
  6428.                
  6429.                 // Apply pattern to pattern composite (like pattern preview)
  6430.                 const tempCanvas = document.createElement("canvas");
  6431.                 const tempCtx = tempCanvas.getContext("2d");
  6432.                 tempCanvas.width = canvasWidth;
  6433.                 tempCanvas.height = canvasHeight;
  6434.                
  6435.                 // Draw the pattern image at full resolution
  6436.                 tempCtx.drawImage(layerImg, 0, 0, canvasWidth, canvasHeight);
  6437.                
  6438.                 // Get the layer's color from appState (pattern layers start at index 1 after Background)
  6439.                 const colorIndex = i + 1; // Skip Background layer at index 0
  6440.                 const layerColor = lookupColor(appState.currentLayers[colorIndex]?.color || "#FFFFFF");
  6441.                 console.log(`๐ŸŽจ Using color ${layerColor} for pattern layer ${i} (color index ${colorIndex})`);
  6442.                
  6443.                 // Parse pattern color (hex to RGB)
  6444.                 const colorMatch = layerColor.match(/^#([0-9a-f]{6})$/i);
  6445.                 if (!colorMatch) {
  6446.                     console.warn(`โš ๏ธ Invalid color format for layer ${i}: ${layerColor}`);
  6447.                     continue;
  6448.                 }
  6449.                
  6450.                 const colorR = parseInt(colorMatch[1].substr(0, 2), 16);
  6451.                 const colorG = parseInt(colorMatch[1].substr(2, 2), 16);
  6452.                 const colorB = parseInt(colorMatch[1].substr(4, 2), 16);
  6453.                
  6454.                 // Apply color vibrance adjustment
  6455.                 const vibrance = fabricTuning.colorVibrance;
  6456.                 const vibranceR = Math.floor(127 + (colorR - 127) * vibrance);
  6457.                 const vibranceG = Math.floor(127 + (colorG - 127) * vibrance);
  6458.                 const vibranceB = Math.floor(127 + (colorB - 127) * vibrance);
  6459.                
  6460.                 console.log(`๐ŸŽจ Pattern layer ${i} RGB: ${vibranceR}, ${vibranceG}, ${vibranceB}`);
  6461.                
  6462.                 // Extract pattern luminance and apply color (like pattern preview)
  6463.                 const imageData = tempCtx.getImageData(0, 0, canvasWidth, canvasHeight);
  6464.                 const data = imageData.data;
  6465.                
  6466.                 let nonTransparentPixels = 0;
  6467.                 let averageLuminance = 0;
  6468.                
  6469.                 // Apply pattern processing (similar to pattern preview)
  6470.                 for (let j = 0; j < data.length; j += 4) {
  6471.                     const r = data[j];
  6472.                     const g = data[j + 1];
  6473.                     const b = data[j + 2];
  6474.                     const alpha = data[j + 3];
  6475.                    
  6476.                     if (alpha > 0) {
  6477.                         nonTransparentPixels++;
  6478.                        
  6479.                         // Calculate pattern luminance
  6480.                         let patternLuminance = 0.299 * r + 0.587 * g + 0.114 * b;
  6481.                        
  6482.                         // Apply pattern contrast adjustment
  6483.                         patternLuminance = Math.pow(patternLuminance / 255, 1 / fabricTuning.patternContrast) * 255;
  6484.                         averageLuminance += patternLuminance;
  6485.                        
  6486.                         // Create colored pattern with luminance-based opacity
  6487.                         const opacity = (patternLuminance / 255) * fabricTuning.alphaStrength;
  6488.                        
  6489.                         data[j] = vibranceR;
  6490.                         data[j + 1] = vibranceG;
  6491.                         data[j + 2] = vibranceB;
  6492.                         data[j + 3] = Math.min(255, opacity * 255);
  6493.                     } else {
  6494.                         data[j + 3] = 0;
  6495.                     }
  6496.                 }
  6497.                
  6498.                 if (nonTransparentPixels > 0) {
  6499.                     averageLuminance /= nonTransparentPixels;
  6500.                     console.log(`๐Ÿ” Pattern layer ${i}: ${nonTransparentPixels} pixels, avg luminance: ${averageLuminance.toFixed(2)}`);
  6501.                 } else {
  6502.                     console.warn(`โš ๏ธ Pattern layer ${i}: No non-transparent pixels found`);
  6503.                 }
  6504.                
  6505.                 // Put the processed pattern back
  6506.                 tempCtx.putImageData(imageData, 0, 0);
  6507.                
  6508.                 // Apply to base canvas using normal blending
  6509.                 baseCtx.globalCompositeOperation = "source-over";
  6510.                 baseCtx.drawImage(tempCanvas, 0, 0);
  6511.                
  6512.                 console.log(`๐Ÿ” Applied pattern layer ${i} to base canvas`);
  6513.                
  6514.                 console.log(`โœ… Pattern layer ${i} (${layerFileName}) applied`);
  6515.                
  6516.             } catch (error) {
  6517.                 console.warn(`โš ๏ธ Pattern layer ${i} (${layerFileName}) failed:`, error);
  6518.             }
  6519.         }
  6520.        
  6521.         // 4. Final compositing in correct order
  6522.         console.log("๐Ÿงต Final compositing: mockup -> base -> patterns -> fabric shadows");
  6523.        
  6524.         // Layer 1: Mockup (unaltered room background)
  6525.         ctx.drawImage(mockupBg, 0, 0);
  6526.        
  6527.         // Layer 2: Base + Patterns (composited with alpha channel)
  6528.         ctx.globalCompositeOperation = "source-over";
  6529.         ctx.drawImage(baseCanvas, 0, 0);
  6530.        
  6531.         // Layer 3: Fabric base for shadows (multiply to bring shadows back)
  6532.         ctx.globalCompositeOperation = "multiply";
  6533.         ctx.drawImage(fabricBase, 0, 0, canvasWidth, canvasHeight);
  6534.        
  6535.         // Layer 4: Glossy finish (screen blend for shine effect)
  6536.         if (fabricTuning.glossyStrength > 0) {
  6537.             try {
  6538.                 const fabricGlossy = new Image();
  6539.                 fabricGlossy.crossOrigin = "anonymous";
  6540.                
  6541.                 await new Promise((resolve, reject) => {
  6542.                     fabricGlossy.onload = resolve;
  6543.                     fabricGlossy.onerror = reject;
  6544.                     // Use fabric-glossy.png from the same directory as fabric-base.png
  6545.                     const glossyPath = fabricConfig.base.replace('fabric-base.png', 'fabric-glossy.png');
  6546.                     fabricGlossy.src = `https://so-animation.com/colorflex/${glossyPath}`;
  6547.                 });
  6548.                
  6549.                 console.log(`๐Ÿ“ Fabric glossy: ${fabricGlossy.width}x${fabricGlossy.height}`);
  6550.                
  6551.                 // Apply glossy layer with screen blend mode and tunable opacity
  6552.                 ctx.globalCompositeOperation = "screen";
  6553.                 ctx.globalAlpha = fabricTuning.glossyStrength;
  6554.                 ctx.drawImage(fabricGlossy, 0, 0, canvasWidth, canvasHeight);
  6555.                
  6556.                 // Reset alpha and composite operation
  6557.                 ctx.globalAlpha = 1.0;
  6558.                 ctx.globalCompositeOperation = "source-over";
  6559.                
  6560.                 console.log("โœ… Glossy layer applied with screen blend");
  6561.                
  6562.             } catch (error) {
  6563.                 console.warn("โš ๏ธ Glossy layer failed to load:", error);
  6564.                 // Continue without glossy layer if it fails
  6565.             }
  6566.         }
  6567.        
  6568.         // Reset composite operation
  6569.         ctx.globalCompositeOperation = "source-over";
  6570.        
  6571.         console.log("โœ… All layers composited in correct order");
  6572.        
  6573.         // Update display - try both possible element references
  6574.         let roomMockup = document.getElementById('roomMockup');
  6575.         if (!roomMockup && dom?.roomMockup) {
  6576.             roomMockup = dom.roomMockup;
  6577.         }
  6578.        
  6579.         console.log("๐Ÿ” roomMockup element found:", !!roomMockup);
  6580.         console.log("๐Ÿ” dom.roomMockup available:", !!dom?.roomMockup);
  6581.        
  6582.         if (roomMockup) {
  6583.             const dataURL = canvas.toDataURL();
  6584.             console.log("๐Ÿ” Canvas dataURL length:", dataURL.length);
  6585.             console.log("๐Ÿ” roomMockup element type:", roomMockup.tagName);
  6586.            
  6587.             // Check if it's an img or div element
  6588.             if (roomMockup.tagName === 'IMG') {
  6589.                 roomMockup.src = dataURL;
  6590.                 console.log("โœ… Set fabric mockup as img src");
  6591.             } else {
  6592.                 // It's a div - preserve back button but clear other content
  6593.                 console.log("๐Ÿ” Div innerHTML before:", roomMockup.innerHTML.substring(0, 100));
  6594.                
  6595.                 // Save existing back button if it exists
  6596.                 const existingButton = roomMockup.querySelector('#backToPatternsBtn');
  6597.                
  6598.                 // Clear the div content
  6599.                 roomMockup.innerHTML = '';
  6600.                
  6601.                 // Clear the CSS background color to make background image visible
  6602.                 roomMockup.style.backgroundColor = 'transparent';
  6603.                
  6604.                 // Set background image
  6605.                 roomMockup.style.backgroundImage = `url(${dataURL})`;
  6606.                 roomMockup.style.backgroundSize = 'contain';
  6607.                 roomMockup.style.backgroundRepeat = 'no-repeat';
  6608.                 roomMockup.style.backgroundPosition = 'center';
  6609.                
  6610.                 // Restore the back button if it existed
  6611.                 if (existingButton) {
  6612.                     roomMockup.appendChild(existingButton);
  6613.                     console.log("โœ… Restored back button after clearing div");
  6614.                 }
  6615.                
  6616.                 console.log("โœ… Set fabric mockup as div background and cleared other content");
  6617.             }
  6618.            
  6619.             console.log("โœ… Fabric mockup displayed to element:", roomMockup.id);
  6620.         } else {
  6621.             console.error("โŒ No roomMockup element found!");
  6622.         }
  6623.        
  6624.         // Add back button for fabric mode (but only if not already present)
  6625.         if (!document.getElementById('backToPatternsBtn')) {
  6626.             addBackToPatternsButton();
  6627.         }
  6628.        
  6629.         // Add fabric tuning controls
  6630.         addFabricTuningControls();
  6631.        
  6632.     } catch (error) {
  6633.         console.error("โŒ Fabric mockup error:", error);
  6634.     }
  6635. }
  6636.  
  6637. // Add Try Fabric button functionality
  6638. function addTryFabricButton() {
  6639.     console.log("๐Ÿงต addTryFabricButton called");
  6640.     console.log("๐Ÿงต selectedCollection:", appState.selectedCollection?.name);
  6641.    
  6642.     // Check if we're in a compatible collection for fabric
  6643.     if (!appState.selectedCollection || appState.selectedCollection.name !== "botanicals") {
  6644.         console.log("๐Ÿงต Not botanicals collection, skipping fabric button");
  6645.         return;
  6646.     }
  6647.    
  6648.     console.log("๐Ÿงต Creating Try Fabric button");
  6649.    
  6650.     const existingButton = document.getElementById('tryFabricBtn');
  6651.     if (existingButton) {
  6652.         existingButton.remove();
  6653.     }
  6654.    
  6655.     const button = document.createElement('button');
  6656.     button.id = 'tryFabricBtn';
  6657.     button.textContent = 'Try Fabric';
  6658.     button.className = 'btn btn-primary';
  6659.     button.style.cssText = `
  6660.         margin-top: 10px;
  6661.         padding: 8px 16px;
  6662.         background-color: #007bff;
  6663.         color: white;
  6664.         border: none;
  6665.         border-radius: 4px;
  6666.         cursor: pointer;
  6667.     `;
  6668.    
  6669.     button.addEventListener('click', () => {
  6670.         console.log("๐Ÿงต ================================");
  6671.         console.log("๐Ÿงต TRY FABRIC BUTTON CLICKED");
  6672.         console.log("๐Ÿงต ================================");
  6673.         renderFabricMockup();
  6674.     });
  6675.    
  6676.     // Add button to the appropriate location
  6677.     const tryFurnitureBtn = document.getElementById('tryFurnitureBtn');
  6678.     if (tryFurnitureBtn) {
  6679.         tryFurnitureBtn.parentNode.insertBefore(button, tryFurnitureBtn.nextSibling);
  6680.     } else {
  6681.         const controlsContainer = document.querySelector('.controls-container') || document.body;
  6682.         controlsContainer.appendChild(button);
  6683.     }
  6684. }
  6685.  
  6686. // Add this line at the bottom of your CFM.js file to expose the function globally:
  6687. window.addTryFurnitureButton = addTryFurnitureButton;
  6688. window.getCompatibleFurniture = getCompatibleFurniture;
  6689. window.showFurnitureModal = showFurnitureModal;
  6690. window.selectFurniture = selectFurniture;
  6691. window.renderFabricMockup = renderFabricMockup;
  6692. window.addTryFabricButton = addTryFabricButton;
  6693.  
  6694. // Debug function to manually test fabric
  6695. window.testFabric = function() {
  6696.     console.log("๐Ÿงต Manual fabric test called");
  6697.     renderFabricMockup();
  6698. };
  6699.  
  6700. // Simple red canvas test
  6701. window.testRedCanvas = function() {
  6702.     console.log("๐Ÿ”ด Testing red canvas display");
  6703.     const canvas = document.createElement("canvas");
  6704.     const ctx = canvas.getContext("2d");
  6705.     canvas.width = 600;
  6706.     canvas.height = 450;
  6707.    
  6708.     ctx.fillStyle = "red";
  6709.     ctx.fillRect(0, 0, 600, 450);
  6710.    
  6711.     ctx.fillStyle = "white";
  6712.     ctx.font = "48px Arial";
  6713.     ctx.fillText("FABRIC TEST", 150, 250);
  6714.    
  6715.     const roomMockup = document.getElementById('roomMockup') || dom?.roomMockup;
  6716.     if (roomMockup) {
  6717.         roomMockup.src = canvas.toDataURL();
  6718.         console.log("๐Ÿ”ด Red canvas set to roomMockup");
  6719.     } else {
  6720.         console.error("โŒ No roomMockup element found");
  6721.     }
  6722. };
  6723.  
  6724. // Simple fabric function that just fits a 3840x2160 image into 600x450
  6725. window.simpleFabricTest = function() {
  6726.     console.log("๐Ÿงต SIMPLE FABRIC TEST");
  6727.    
  6728.     const canvas = document.createElement("canvas");
  6729.     const ctx = canvas.getContext("2d");
  6730.     canvas.width = 600;
  6731.     canvas.height = 450;
  6732.    
  6733.     // Fill with a color first
  6734.     ctx.fillStyle = "#F0F0E9";
  6735.     ctx.fillRect(0, 0, 600, 450);
  6736.    
  6737.     const img = new Image();
  6738.     img.crossOrigin = "anonymous";
  6739.     img.onload = function() {
  6740.         console.log(`Image loaded: ${img.width}x${img.height}`);
  6741.        
  6742.         // Calculate scale to fit 3840x2160 into 600x450
  6743.         const scaleX = 600 / img.width;
  6744.         const scaleY = 450 / img.height;
  6745.         const scale = Math.min(scaleX, scaleY);
  6746.        
  6747.         console.log(`Scale: ${scale} (${scaleX}, ${scaleY})`);
  6748.        
  6749.         const w = img.width * scale;
  6750.         const h = img.height * scale;
  6751.         const x = (600 - w) / 2;
  6752.         const y = (450 - h) / 2;
  6753.        
  6754.         console.log(`Drawing at: ${x}, ${y}, ${w}x${h}`);
  6755.        
  6756.         ctx.drawImage(img, x, y, w, h);
  6757.        
  6758.         // Update display
  6759.         const roomMockup = document.getElementById('roomMockup');
  6760.         if (roomMockup) {
  6761.             roomMockup.src = canvas.toDataURL();
  6762.             console.log("โœ… Simple fabric test complete");
  6763.         }
  6764.     };
  6765.    
  6766.     img.src = "https://so-animation.com/colorflex/data/fabric/fabric-base.png";
  6767. };
  6768. window.addBackToPatternsButton = addBackToPatternsButton;
  6769. window.initializeTryFurnitureFeature = initializeTryFurnitureFeature;
Advertisement
Add Comment
Please, Sign In to add comment