Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- // Create a dimensions display element
- // const dimensionsDisplay = document.createElement('div');
- // dimensionsDisplay.id = 'dimensions-display';
- // dimensionsDisplay.style.cssText = `
- // position: fixed;
- // top: 10px;
- // right: 10px;
- // background: rgba(0, 0, 0, 0.7);
- // color: white;
- // padding: 5px 10px;
- // font-size: 14px;
- // font-family: monospace;
- // z-index: 1000;
- // border-radius: 3px;
- // `;
- // document.body.appendChild(dimensionsDisplay);
- // // Function to update dimensions in the UI
- // const updateDimensionsDisplay = () => {
- // const width = window.innerWidth;
- // const height = window.innerHeight;
- // dimensionsDisplay.textContent = `${width} x ${height}px`;
- // };
- // updateDimensionsDisplay();
- // window.addEventListener('resize', updateDimensionsDisplay);
- // Wrap your key functions:
- function traceWrapper(fn, name) {
- return function(...args) {
- console.group(`๐ง ${name}`);
- console.log('Arguments:', args);
- const result = fn.apply(this, args);
- console.log('Result:', result);
- console.groupEnd();
- return result;
- };
- }
- // Then guard against premature loading:
- function guard(fn) {
- return function (...args) {
- if (!isAppReady) {
- console.warn(`โณ Skipping ${fn.name} โ app not ready`);
- return;
- }
- return fn.apply(this, args);
- };
- }
- // ---- Debug Logging Setup ----
- const DEBUG_TRACE = false; // set to false to disable tracing
- const USE_GUARD = false;
- // Optional: Remove later by commenting out or deleting these lines// Toggle flag for normalization (set to false for binary threshold, true for normalization)
- const USE_NORMALIZATION = true; // Change to true to enable normalization
- // Fabric composite tuning parameters
- const fabricTuning = {
- alphaStrength: 1.0, // Controls pattern opacity (0.0 - 2.0)
- baseTintStrength: 1.0, // Controls how much background color affects fabric base (0.0 - 2.0)
- patternContrast: 1.0, // Controls pattern contrast (0.0 - 3.0)
- shadowMultiplier: 1.0, // Controls shadow interaction strength (0.0 - 2.0)
- colorVibrance: 1.2, // Controls color saturation (0.0 - 2.0)
- blendMode: 'auto', // Blend mode: 'multiply', 'overlay', 'soft-light', 'auto'
- glossyStrength: 1.0 // Controls glossy layer opacity (0.0 - 2.0)
- };
- // Control visibility of fabric tuning controls
- const SHOW_FABRIC_CONTROLS = false; // Set to true to show controls, false to hide
- // Debounce function for tuning controls
- let fabricRenderTimeout;
- function debouncedFabricRender() {
- clearTimeout(fabricRenderTimeout);
- fabricRenderTimeout = setTimeout(() => {
- if (appState.isInFabricMode) {
- renderFabricMockup();
- }
- }, 100); // 100ms debounce
- }
- // App state - Made global for save functionality
- window.appState = {
- collections: [],
- colorsData: [],
- currentPattern: null,
- currentLayers: [],
- curatedColors: [],
- layerInputs: [],
- selectedCollection: null,
- cachedLayerPaths: [],
- lastSelectedLayer: null,
- currentScale: 10,
- designer_colors: [],
- originalPattern: null,
- originalCoordinates: null,
- originalLayerInputs: null,
- originalCurrentLayers: null,
- lastSelectedColor: null,
- selectedFurniture: null,
- isInFabricMode: false
- };
- const BACKGROUND_INDEX = 0;
- const FURNITURE_BASE_INDEX = 1;
- const PATTERN_BASE_INDEX = 2;
- let isAppReady = false; // Flag to track if the app is fully initialized
- // Save to list functionality - Updated: 2025-01-19 v3 - Fixed syntax
- window.saveToMyList = function() {
- try {
- // Use global appState reference
- const state = window.appState;
- // Validate that we have the required data
- if (!state.currentPattern || !state.currentPattern.name) {
- showSaveNotification('โ No pattern selected to save');
- return;
- }
- if (!state.selectedCollection || !state.selectedCollection.name) {
- showSaveNotification('โ No collection selected');
- return;
- }
- if (!state.currentLayers || state.currentLayers.length === 0) {
- showSaveNotification('โ No layers to save');
- return;
- }
- // Capture current pattern state
- const currentState = {
- pattern: {
- name: state.currentPattern.name,
- collection: state.selectedCollection.name,
- layers: state.currentLayers.map(layer => ({
- label: layer.label,
- color: layer.color,
- isShadow: layer.isShadow || false
- }))
- },
- timestamp: new Date().toISOString(),
- id: Date.now() // Simple ID generation
- };
- console.log('๐พ Saving pattern to list:', currentState);
- // Try to save to Shopify customer metafields (if available)
- const customerId = getCustomerId();
- const customerAccessToken = getCustomerAccessToken();
- if (customerId && customerAccessToken) {
- saveToShopifyMetafields(currentState).then(function() {
- console.log('โ Saved to Shopify customer metafields');
- }).catch(function(error) {
- console.log('๐ Shopify save failed, using localStorage fallback');
- saveToLocalStorage(currentState);
- });
- } else {
- // Fall back to localStorage for development/testing
- console.log('๐ฑ Customer not authenticated, saving to localStorage');
- saveToLocalStorage(currentState);
- }
- // Show success message
- showSaveNotification('โ Pattern saved to your list!');
- } catch (error) {
- console.error('โ Failed to save pattern:', error);
- showSaveNotification('โ Failed to save pattern');
- }
- };
- // Save to Shopify customer metafields
- function saveToShopifyMetafields(patternData) {
- return new Promise(function(resolve, reject) {
- try {
- var customerId = getCustomerId();
- var customerAccessToken = getCustomerAccessToken();
- if (!customerId || !customerAccessToken) {
- reject(new Error('Customer not authenticated'));
- return;
- }
- console.log('๐ Saving to Shopify customer metafields...');
- fetch('/api/colorFlex/save-pattern', {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- 'X-Shopify-Customer-Access-Token': customerAccessToken
- },
- body: JSON.stringify({
- customerId: customerId,
- patternData: patternData
- })
- }).then(function(response) {
- if (!response.ok) {
- response.json().then(function(errorData) {
- reject(new Error(errorData.message || 'Failed to save to Shopify'));
- }).catch(function() {
- reject(new Error('Failed to save to Shopify'));
- });
- return;
- }
- response.json().then(function(result) {
- console.log('โ Pattern saved to Shopify metafields:', result);
- resolve(result);
- }).catch(function(error) {
- reject(error);
- });
- }).catch(function(error) {
- console.error('โ Shopify save failed:', error);
- // Fallback to localStorage
- console.log('๐ Falling back to localStorage...');
- saveToLocalStorage(patternData);
- reject(error);
- });
- } catch (error) {
- console.error('โ Shopify save failed:', error);
- // Fallback to localStorage
- console.log('๐ Falling back to localStorage...');
- saveToLocalStorage(patternData);
- reject(error);
- }
- });
- }
- // Save to localStorage as fallback
- function saveToLocalStorage(patternData) {
- const existingPatterns = JSON.parse(localStorage.getItem('colorFlex_saved_patterns') || '[]');
- existingPatterns.push(patternData);
- // Limit to last 20 patterns
- const limitedPatterns = existingPatterns.slice(-20);
- localStorage.setItem('colorFlex_saved_patterns', JSON.stringify(limitedPatterns));
- }
- // Helper functions
- function getShopifyMetafield(key) {
- // In a real Shopify app, this would fetch from customer metafields
- return JSON.parse(localStorage.getItem('colorFlex_saved_patterns') || '[]');
- }
- function getCustomerId() {
- // Get from Shopify customer object or URL params
- if (window.ShopifyCustomer && window.ShopifyCustomer.id) {
- return window.ShopifyCustomer.id;
- }
- // Check for Liquid template customer ID
- if (typeof window.customer !== 'undefined' && window.customer.id) {
- return window.customer.id;
- }
- // Fallback to localStorage for development
- return localStorage.getItem('development_customer_id') || null;
- }
- function getCustomerAccessToken() {
- // Get from Shopify customer access token
- if (window.ShopifyCustomer && window.ShopifyCustomer.access_token) {
- return window.ShopifyCustomer.access_token;
- }
- // Check for global customer access token
- if (window.customerAccessToken) {
- return window.customerAccessToken;
- }
- // Fallback for development
- return localStorage.getItem('development_customer_token') || null;
- }
- function showSaveNotification(message) {
- // Create notification element
- const notification = document.createElement('div');
- notification.style.cssText = `
- position: fixed;
- top: 20px;
- right: 20px;
- background: ${message.includes('โ ') ? '#48bb78' : '#f56565'};
- color: white;
- padding: 12px 20px;
- border-radius: 8px;
- font-family: 'Special Elite', monospace;
- font-size: 14px;
- font-weight: bold;
- z-index: 10000;
- box-shadow: 0 4px 12px rgba(0,0,0,0.3);
- animation: slideIn 0.3s ease-out;
- `;
- notification.textContent = message;
- // Add CSS animation
- const style = document.createElement('style');
- style.textContent = `
- @keyframes slideIn {
- from { transform: translateX(100%); opacity: 0; }
- to { transform: translateX(0); opacity: 1; }
- }
- `;
- document.head.appendChild(style);
- document.body.appendChild(notification);
- // Remove after 3 seconds
- setTimeout(() => {
- notification.remove();
- style.remove();
- }, 3000);
- }
- // Add save button to pattern preview
- function addSaveButton() {
- // Check if button already exists
- if (document.getElementById('saveToListBtn')) {
- return;
- }
- // Find pattern preview container
- const patternPreview = document.getElementById('patternPreview') || document.querySelector('#patternPreviewWrapper');
- if (!patternPreview) {
- console.warn('โ ๏ธ Pattern preview container not found for save button');
- return;
- }
- // Create save button
- const saveButton = document.createElement('button');
- saveButton.id = 'saveToListBtn';
- saveButton.innerHTML = '๐พ Save to My List';
- saveButton.style.cssText = `
- position: absolute;
- top: 10px;
- right: 10px;
- background: linear-gradient(135deg, #48bb78 0%, #38a169 100%);
- color: white;
- border: none;
- padding: 10px 16px;
- border-radius: 20px;
- font-family: 'Special Elite', monospace;
- font-size: 12px;
- font-weight: bold;
- cursor: pointer;
- box-shadow: 0 3px 10px rgba(0,0,0,0.2);
- transition: all 0.3s ease;
- z-index: 100;
- `;
- // Add hover effect
- saveButton.addEventListener('mouseenter', () => {
- saveButton.style.transform = 'translateY(-2px)';
- saveButton.style.boxShadow = '0 5px 15px rgba(0,0,0,0.3)';
- });
- saveButton.addEventListener('mouseleave', () => {
- saveButton.style.transform = 'translateY(0)';
- saveButton.style.boxShadow = '0 3px 10px rgba(0,0,0,0.2)';
- });
- // Add click handler
- saveButton.addEventListener('click', saveToMyList);
- // Create "View Saved" button
- const viewSavedButton = document.createElement('button');
- viewSavedButton.id = 'viewSavedBtn';
- viewSavedButton.innerHTML = '๐ View Saved';
- viewSavedButton.style.cssText = `
- position: absolute;
- top: 50px;
- right: 10px;
- background: linear-gradient(135deg, #2d3748 0%, #1a202c 100%);
- color: white;
- border: none;
- padding: 10px 16px;
- border-radius: 20px;
- font-family: 'Special Elite', monospace;
- font-size: 12px;
- font-weight: bold;
- cursor: pointer;
- box-shadow: 0 3px 10px rgba(0,0,0,0.2);
- transition: all 0.3s ease;
- z-index: 100;
- `;
- // Add hover effect for view button
- viewSavedButton.addEventListener('mouseenter', function() {
- viewSavedButton.style.transform = 'translateY(-2px)';
- viewSavedButton.style.boxShadow = '0 5px 15px rgba(0,0,0,0.3)';
- });
- viewSavedButton.addEventListener('mouseleave', function() {
- viewSavedButton.style.transform = 'translateY(0)';
- viewSavedButton.style.boxShadow = '0 3px 10px rgba(0,0,0,0.2)';
- });
- // Add click handler for view saved patterns
- viewSavedButton.addEventListener('click', showSavedPatternsModal);
- // Add to pattern preview container
- patternPreview.style.position = 'relative'; // Ensure relative positioning
- patternPreview.appendChild(saveButton);
- patternPreview.appendChild(viewSavedButton);
- console.log('โ Save and view buttons added to pattern preview');
- }
- // Show saved patterns modal
- function showSavedPatternsModal() {
- try {
- console.log('๐ Loading saved patterns...');
- // Get saved patterns from localStorage (will add Shopify support later)
- var savedPatterns = JSON.parse(localStorage.getItem('colorFlex_saved_patterns') || '[]');
- console.log('๐ฑ Loaded patterns from localStorage:', savedPatterns.length);
- createSavedPatternsModal(savedPatterns);
- } catch (error) {
- console.error('โ Error loading saved patterns:', error);
- showSaveNotification('โ Failed to load saved patterns');
- }
- }
- // Create saved patterns modal
- function createSavedPatternsModal(patterns) {
- // Remove existing modal
- var existingModal = document.getElementById('savedPatternsModal');
- if (existingModal) {
- existingModal.remove();
- }
- // Create modal overlay
- var modal = document.createElement('div');
- modal.id = 'savedPatternsModal';
- modal.style.cssText = `
- position: fixed;
- top: 0;
- left: 0;
- width: 100%;
- height: 100%;
- background: rgba(0,0,0,0.8);
- z-index: 10000;
- display: flex;
- justify-content: center;
- align-items: center;
- `;
- // Create modal content
- var modalContent = document.createElement('div');
- modalContent.style.cssText = `
- background: #1a202c;
- color: white;
- padding: 20px;
- border-radius: 10px;
- max-width: 600px;
- max-height: 80vh;
- overflow-y: auto;
- font-family: 'Special Elite', monospace;
- border: 2px solid #4a5568;
- `;
- // Modal header
- var header = document.createElement('div');
- header.style.cssText = 'display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; border-bottom: 1px solid #4a5568; padding-bottom: 10px;';
- var title = document.createElement('h2');
- title.textContent = '๐ My Saved Patterns (' + patterns.length + ')';
- title.style.margin = '0';
- title.style.color = '#f0e6d2';
- var closeBtn = document.createElement('button');
- closeBtn.textContent = 'ร';
- closeBtn.style.cssText = `
- background: none;
- border: none;
- color: white;
- font-size: 24px;
- cursor: pointer;
- padding: 0;
- width: 30px;
- height: 30px;
- border-radius: 50%;
- background: #f56565;
- `;
- closeBtn.addEventListener('click', function() { modal.remove(); });
- header.appendChild(title);
- header.appendChild(closeBtn);
- modalContent.appendChild(header);
- // Patterns list
- if (patterns.length === 0) {
- var emptyMessage = document.createElement('div');
- emptyMessage.innerHTML = `
- <div style="text-align: center; padding: 40px; color: #a0aec0;">
- <div style="font-size: 48px; margin-bottom: 20px;">๐จ</div>
- <h3>No saved patterns yet</h3>
- <p>Start customizing patterns and save your favorites!</p>
- </div>
- `;
- modalContent.appendChild(emptyMessage);
- } else {
- for (var i = 0; i < patterns.length; i++) {
- var patternDiv = createSavedPatternItem(patterns[i], i);
- modalContent.appendChild(patternDiv);
- }
- }
- modal.appendChild(modalContent);
- document.body.appendChild(modal);
- // Close on overlay click
- modal.addEventListener('click', function(e) {
- if (e.target === modal) {
- modal.remove();
- }
- });
- }
- // Create individual saved pattern item
- function createSavedPatternItem(pattern, index) {
- var item = document.createElement('div');
- item.style.cssText = `
- border: 1px solid #4a5568;
- border-radius: 8px;
- padding: 15px;
- margin-bottom: 10px;
- background: #2d3748;
- transition: background 0.3s ease;
- `;
- // Hover effect
- item.addEventListener('mouseenter', function() {
- item.style.background = '#374151';
- });
- item.addEventListener('mouseleave', function() {
- item.style.background = '#2d3748';
- });
- var info = document.createElement('div');
- info.innerHTML = `
- <div style="display: flex; justify-content: space-between; align-items: start; margin-bottom: 10px;">
- <div>
- <strong style="color: #f0e6d2; font-size: 16px;">๐จ ${pattern.pattern.name}</strong><br>
- <small style="color: #a0aec0;">๐ Collection: ${pattern.pattern.collection}</small><br>
- <small style="color: #a0aec0;">๐ Saved: ${new Date(pattern.timestamp).toLocaleDateString()}</small><br>
- <small style="color: #a0aec0;">๐ฏ Layers: ${pattern.pattern.layers.length}</small>
- </div>
- <div style="font-size: 12px; color: #68d391; background: #22543d; padding: 4px 8px; border-radius: 12px;">
- ID: ${pattern.id}
- </div>
- </div>
- `;
- // Show layer colors
- if (pattern.pattern.layers && pattern.pattern.layers.length > 0) {
- var layersDiv = document.createElement('div');
- layersDiv.style.cssText = 'margin: 10px 0; padding: 8px; background: #1a202c; border-radius: 4px; border-left: 3px solid #4299e1;';
- layersDiv.innerHTML = '<small style="color: #4299e1; font-weight: bold;">Layer Colors:</small><br>';
- for (var i = 0; i < pattern.pattern.layers.length; i++) {
- var layer = pattern.pattern.layers[i];
- layersDiv.innerHTML += '<small style="color: #e2e8f0;">โข ' + layer.label + ': <span style="color: ' + layer.color + '; font-weight: bold;">' + layer.color + '</span></small><br>';
- }
- info.appendChild(layersDiv);
- }
- var buttons = document.createElement('div');
- buttons.style.cssText = 'margin-top: 15px; display: flex; gap: 10px; justify-content: flex-end;';
- // Delete button
- var deleteBtn = document.createElement('button');
- deleteBtn.textContent = '๐๏ธ Delete';
- deleteBtn.style.cssText = `
- background: #f56565;
- color: white;
- border: none;
- padding: 8px 12px;
- border-radius: 4px;
- cursor: pointer;
- font-size: 12px;
- font-family: 'Special Elite', monospace;
- transition: background 0.3s ease;
- `;
- deleteBtn.addEventListener('mouseenter', function() {
- deleteBtn.style.background = '#e53e3e';
- });
- deleteBtn.addEventListener('mouseleave', function() {
- deleteBtn.style.background = '#f56565';
- });
- deleteBtn.addEventListener('click', function() {
- if (confirm('๐๏ธ Delete "' + pattern.pattern.name + '"?\n\nThis action cannot be undone.')) {
- deleteSavedPattern(pattern.id);
- document.getElementById('savedPatternsModal').remove();
- showSavedPatternsModal(); // Refresh modal
- }
- });
- buttons.appendChild(deleteBtn);
- item.appendChild(info);
- item.appendChild(buttons);
- return item;
- }
- // Delete a saved pattern
- function deleteSavedPattern(patternId) {
- try {
- // Delete from localStorage
- var patterns = JSON.parse(localStorage.getItem('colorFlex_saved_patterns') || '[]');
- var updatedPatterns = patterns.filter(function(p) { return p.id !== patternId; });
- localStorage.setItem('colorFlex_saved_patterns', JSON.stringify(updatedPatterns));
- console.log('โ Pattern deleted from localStorage');
- showSaveNotification('โ Pattern deleted successfully!');
- } catch (error) {
- console.error('โ Error deleting pattern:', error);
- showSaveNotification('โ Failed to delete pattern');
- }
- }
- // Path normalization function to fix ./data/ vs data/ inconsistencies
- function normalizePath(path) {
- if (!path || typeof path !== 'string') return path;
- // If it's already a full URL, return as-is
- if (path.startsWith('http://') || path.startsWith('https://')) {
- return path;
- }
- // Convert "./data/" to "data/" for consistency
- if (path.startsWith('./data/')) {
- path = path.substring(2); // Remove the "./"
- }
- // For any other relative paths, ensure they don't start with "./"
- if (path.startsWith('./')) {
- path = path.substring(2);
- }
- // If it's a data/ path, convert to absolute URL
- if (path.startsWith('data/')) {
- return `https://so-animation.com/colorflex/${path}`;
- }
- return path;
- }
- // Store furniture view settings globally for consistency
- const furnitureViewSettings = {
- scale: 0.7,
- offsetX: 0,
- offsetY: -120,
- // Zoom states
- isZoomed: false,
- zoomScale: 2, // 220% zoom when clicked
- zoomX: 0, // Where we're zoomed to
- zoomY: 0 // Where we're zoomed to
- };
- const DEFAULT_FURNITURE_SETTINGS = {
- scale: 0.7,
- offsetX: 0,
- offsetY: -120
- };
- function addInteractiveZoom() {
- console.log("๐ Adding interactive zoom to furniture preview");
- const roomMockup = document.getElementById('roomMockup');
- if (!roomMockup) {
- console.error("โ Room mockup container not found");
- return;
- }
- // โ Add debouncing to prevent rapid clicks
- let isZoomInProgress = false;
- let lastClickTime = 0;
- const MIN_CLICK_INTERVAL = 500; // Minimum 500ms between clicks
- roomMockup.style.cursor = 'pointer';
- roomMockup.onclick = null;
- roomMockup.addEventListener('click', function(e) {
- const currentTime = Date.now();
- // โ Debounce rapid clicks
- if (currentTime - lastClickTime < MIN_CLICK_INTERVAL) {
- console.log("๐ซ Click ignored - too rapid");
- return;
- }
- // โ Prevent overlapping zoom operations
- if (isZoomInProgress) {
- console.log("๐ซ Click ignored - zoom in progress");
- return;
- }
- lastClickTime = currentTime;
- isZoomInProgress = true;
- console.log("๐ฑ๏ธ Room mockup clicked (debounced)");
- const isFurnitureCollection = appState.selectedCollection?.wallMask != null;
- if (!isFurnitureCollection) {
- console.log("Not a furniture collection, ignoring click");
- isZoomInProgress = false;
- return;
- }
- // Get click position
- const rect = roomMockup.getBoundingClientRect();
- const x = e.clientX - rect.left;
- const y = e.clientY - rect.top;
- const clickX = (x / rect.width) * 600;
- const clickY = (y / rect.height) * 450;
- console.log(`๐ฏ Click at canvas coordinates: (${clickX.toFixed(0)}, ${clickY.toFixed(0)})`);
- // โ More robust state detection
- const currentScale = furnitureViewSettings.scale;
- const isCurrentlyZoomed = currentScale > 1.0; // Any scale > 1.0 is considered "zoomed"
- console.log(`๐ Current state - scale: ${currentScale}, considered zoomed: ${isCurrentlyZoomed}`);
- if (isCurrentlyZoomed) {
- // Zoom out to default
- console.log(`๐ Zooming out to default scale (${DEFAULT_FURNITURE_SETTINGS.scale})`);
- furnitureViewSettings.isZoomed = false;
- furnitureViewSettings.scale = DEFAULT_FURNITURE_SETTINGS.scale;
- furnitureViewSettings.offsetX = DEFAULT_FURNITURE_SETTINGS.offsetX;
- furnitureViewSettings.offsetY = DEFAULT_FURNITURE_SETTINGS.offsetY;
- roomMockup.style.cursor = 'zoom-in';
- } else {
- // Zoom in to click point
- console.log(`๐ Zooming in to click point`);
- furnitureViewSettings.isZoomed = true;
- furnitureViewSettings.scale = furnitureViewSettings.zoomScale; // 2.2
- // Proper offset calculation accounting for default offset
- const canvasWidth = 600;
- const canvasHeight = 450;
- const centerX = canvasWidth / 2;
- const centerY = canvasHeight / 2;
- // Calculate how much to offset to center the clicked point
- const defaultScale = 0.7; // Your default scale
- const defaultOffsetX = 0; // Your default offsetX
- const defaultOffsetY = -120; // Your default offsetY
- const scaleFactor = furnitureViewSettings.zoomScale / defaultScale; // 2.2 / 0.7 = 3.14
- // Calculate offset relative to the default position
- furnitureViewSettings.offsetX = defaultOffsetX + (centerX - clickX) * (scaleFactor - 1);
- furnitureViewSettings.offsetY = defaultOffsetY + (centerY - clickY) * (scaleFactor - 1);
- console.log(` Scale factor: ${scaleFactor}`);
- console.log(` Default offset: (${defaultOffsetX}, ${defaultOffsetY})`);
- console.log(` New offset: (${furnitureViewSettings.offsetX.toFixed(0)}, ${furnitureViewSettings.offsetY.toFixed(0)})`);
- roomMockup.style.cursor = 'zoom-out';
- }
- console.log("๐ Calling updateFurniturePreview with new zoom state");
- console.log("๐ Final settings:", JSON.stringify({
- scale: furnitureViewSettings.scale,
- offsetX: furnitureViewSettings.offsetX,
- offsetY: furnitureViewSettings.offsetY,
- isZoomed: furnitureViewSettings.isZoomed
- }, null, 2));
- // โ Call update and reset progress flag when done
- if (typeof updateFurniturePreview === 'function') {
- updateFurniturePreview().then(() => {
- isZoomInProgress = false;
- console.log("โ Zoom operation completed");
- }).catch(error => {
- console.error("โ Zoom operation failed:", error);
- isZoomInProgress = false;
- });
- } else {
- console.error("โ updateFurniturePreview function not found!");
- updateDisplays();
- isZoomInProgress = false;
- }
- });
- // Set initial cursor
- const isFurnitureCollection = window.appState.selectedCollection && window.appState.selectedCollection.wallMask != null;
- if (isFurnitureCollection) {
- const currentScale = furnitureViewSettings.scale;
- const isCurrentlyZoomed = currentScale > 1.0;
- roomMockup.style.cursor = isCurrentlyZoomed ? 'zoom-out' : 'zoom-in';
- console.log("โ Set cursor for furniture collection");
- } else {
- roomMockup.style.cursor = 'default';
- console.log("โ Set default cursor for non-furniture collection");
- }
- console.log("โ Interactive zoom added to room mockup");
- }
- // Also add this debug function to test zoom manually:
- function testZoom() {
- console.log("๐งช Testing zoom functionality");
- console.log("Current furnitureViewSettings:", furnitureViewSettings);
- // Test zoom in
- furnitureViewSettings.isZoomed = true;
- furnitureViewSettings.scale = 2.2;
- furnitureViewSettings.offsetX = -100;
- furnitureViewSettings.offsetY = -50;
- console.log("Updated furnitureViewSettings:", furnitureViewSettings);
- // Trigger re-render
- if (typeof updateFurniturePreview === 'function') {
- console.log("Calling updateFurniturePreview...");
- updateFurniturePreview();
- } else {
- console.error("updateFurniturePreview function not found!");
- }
- }
- // DOM references
- const dom = {
- patternName: document.getElementById("patternName"),
- collectionHeader: document.getElementById("collectionHeader"),
- collectionThumbnails: document.getElementById("collectionThumbnails"),
- layerInputsContainer: document.getElementById("layerInputsContainer"),
- curatedColorsContainer: document.getElementById("curatedColorsContainer"),
- coordinatesContainer: document.getElementById("coordinatesContainer"),
- preview: document.getElementById("preview"),
- roomMockup: document.getElementById("roomMockup"),
- printButton: document.getElementById("printButton") // Assuming a button exists
- };
- // Validate DOM elements and report missing ones
- function validateDOMElements() {
- console.log("๐ DOM Validation:");
- Object.entries(dom).forEach(([key, element]) => {
- if (element) {
- console.log(` โ ${key}: found`);
- } else {
- console.error(` โ ${key}: NOT FOUND - missing element with id "${key}"`);
- }
- });
- }
- // Watch changes to patternName
- const patternNameElement = document.getElementById("patternName");
- Object.defineProperty(dom, 'patternName', {
- get() {
- return patternNameElement;
- },
- set(value) {
- console.log("Setting #patternName to:", value, "Caller:", new Error().stack.split('\n')[2].trim());
- patternNameElement.textContent = value;
- },
- configurable: true
- });
- // Debug function to check what's happening with collection names
- window.debugCollectionName = function() {
- console.log(`๐ COLLECTION NAME DEBUG:`);
- console.log(`========================`);
- console.log(`Current collection name: "${appState.selectedCollection?.name}"`);
- console.log(`Current pattern name: "${appState.currentPattern?.name}"`);
- console.log(`Furniture mode: ${appState.furnitureMode}`);
- if (appState.furnitureMode) {
- console.log(`Original collection: "${appState.originalCollection?.name}"`);
- console.log(`Original collection exists: ${!!appState.originalCollection?.fullCollection}`);
- // Check if we can get the original collection name from the furniture collection
- const originalFromFurniture = appState.selectedCollection?.originalCollectionName;
- console.log(`Original collection from furniture collection: "${originalFromFurniture}"`);
- }
- // Test what the path should be
- if (appState.selectedCollection && appState.currentPattern) {
- let collectionNameForPaths;
- if (appState.furnitureMode) {
- // Try multiple ways to get the original collection name
- collectionNameForPaths = appState.originalCollection?.name
- || appState.selectedCollection?.originalCollectionName
- || "UNKNOWN";
- } else {
- collectionNameForPaths = appState.selectedCollection.name;
- }
- const patternName = appState.currentPattern.name;
- const slug = createPatternSlug(patternName);
- console.log(`Expected path structure:`);
- console.log(` Collection for paths: "${collectionNameForPaths}"`);
- console.log(` Pattern: "${patternName}"`);
- console.log(` Slug: "${slug}"`);
- console.log(` Should be: data/furniture/sofa-capitol/patterns/${collectionNameForPaths}/${slug}/`);
- if (collectionNameForPaths === "UNKNOWN") {
- console.error(`โ Cannot determine original collection name!`);
- console.error(` This is why paths are broken.`);
- }
- }
- return {
- selectedCollection: appState.selectedCollection?.name,
- currentPattern: appState.currentPattern?.name,
- furnitureMode: appState.furnitureMode,
- originalCollection: appState.originalCollection?.name
- };
- };
- window.getAppState = function() {
- return {
- selectedCollection: appState.selectedCollection?.name,
- currentPattern: appState.currentPattern?.name,
- furnitureMode: appState.furnitureMode,
- originalCollection: appState.originalCollection?.name,
- collections: appState.collections?.map(c => c.name),
- furnitureConfigLoaded: !!furnitureConfig
- };
- };
- window.fixOriginalCollection = function(originalCollectionName) {
- console.log(`๐ง QUICK FIX: Setting original collection to "${originalCollectionName}"`);
- if (!appState.originalCollection) {
- appState.originalCollection = {};
- }
- appState.originalCollection.name = originalCollectionName;
- // Also store it in the furniture collection for future reference
- if (appState.selectedCollection) {
- appState.selectedCollection.originalCollectionName = originalCollectionName;
- }
- console.log(`โ Fixed! Original collection name is now: "${appState.originalCollection.name}"`);
- console.log(`Run debugCollectionName() to verify the fix.`);
- return {
- originalCollection: appState.originalCollection.name,
- furnitureCollection: appState.selectedCollection?.originalCollectionName
- };
- };
- // Status check accessible from console
- window.checkStatus = function() {
- console.log(`๐ FURNITURE IMPLEMENTATION STATUS CHECK:`);
- console.log(`======================================`);
- // Check if furniture config is loaded
- if (!furnitureConfig) {
- console.log(`โ furnitureConfig not loaded`);
- return { error: "furnitureConfig not loaded" };
- }
- console.log(`โ furnitureConfig loaded: ${Object.keys(furnitureConfig).length} furniture pieces`);
- // Check collections
- if (!appState.collections || appState.collections.length === 0) {
- console.log(`โ Collections not loaded`);
- return { error: "Collections not loaded" };
- }
- console.log(`โ Collections loaded: ${appState.collections.length} collections`);
- // Check current state
- const currentCollection = appState.selectedCollection?.name;
- if (!currentCollection) {
- console.log(`โ No collection currently selected`);
- return { error: "No collection selected" };
- }
- console.log(`โ Current collection: ${currentCollection}`);
- // Check compatibility
- const compatible = getCompatibleFurniture(currentCollection);
- console.log(`โ Compatible furniture: ${compatible.length} pieces`);
- compatible.forEach(f => console.log(` - ${f.name}`));
- // Check if Try Furniture button should be visible
- const tryButton = document.getElementById('tryFurnitureBtn');
- const backButton = document.getElementById('backToPatternsBtn');
- if (appState.furnitureMode) {
- console.log(`๐ช Currently in FURNITURE MODE`);
- console.log(` Back button present: ${!!backButton}`);
- } else {
- console.log(`๐จ Currently in PATTERN MODE`);
- console.log(` Try Furniture button present: ${!!tryButton}`);
- if (!tryButton && compatible.length > 0) {
- console.log(`โ ๏ธ Try Furniture button should be visible but isn't!`);
- }
- }
- return {
- furnitureConfigLoaded: !!furnitureConfig,
- collectionsLoaded: appState.collections?.length > 0,
- currentCollection: currentCollection,
- compatibleFurniture: compatible.length,
- furnitureMode: appState.furnitureMode,
- tryButtonPresent: !!tryButton,
- backButtonPresent: !!backButton,
- originalCollection: appState.originalCollection?.name
- };
- };
- function ensureButtonsAfterUpdate() {
- // Small delay to ensure DOM update is complete
- setTimeout(() => {
- if (!appState.furnitureMode && !document.getElementById('tryFurnitureBtn')) {
- if (window.COLORFLEX_DEBUG) {
- console.log("๐ Re-adding Try Fabric button after room mockup update");
- }
- addTryFurnitureButton();
- }
- if (appState.furnitureMode && !document.getElementById('backToPatternsBtn')) {
- if (window.COLORFLEX_DEBUG) {
- console.log("๐ Re-adding Back to Patterns button after room mockup update");
- }
- addBackToPatternsButton();
- }
- }, 50);
- }
- // Test pattern slug generation
- window.testSlug = function(patternName) {
- const slug = createPatternSlug(patternName);
- console.log(`Pattern: "${patternName}" โ Slug: "${slug}"`);
- return slug;
- };
- // Simple state viewer
- window.viewState = function() {
- const state = {
- selectedCollection: appState.selectedCollection?.name,
- currentPattern: appState.currentPattern?.name,
- furnitureMode: appState.furnitureMode,
- originalCollection: appState.originalCollection?.name,
- patterns: appState.selectedCollection?.patterns?.length,
- furnitureConfig: Object.keys(furnitureConfig || {})
- };
- console.table(state);
- return state;
- };
- // Debug functions available in development mode only
- if (window.location.hostname === 'localhost' || window.location.hostname.includes('dev')) {
- console.log(`
- ๐ง DEBUG FUNCTIONS LOADED!
- =========================
- Available console commands:
- โข debugCollectionName() - Debug collection name issues
- โข fixOriginalCollection("botanicals") - Quick fix for collection name
- โข checkStatus() - Check implementation status
- โข viewState() - View current app state
- โข testSlug("Pattern Name") - Test slug conversion
- โข getAppState() - Get simplified app state
- Try running: debugCollectionName()
- `);
- }
- // Create pattern slug from pattern name
- function createPatternSlug(patternName) {
- return patternName
- .toLowerCase()
- .replace(/[^a-z0-9\s-]/g, '') // Remove special characters
- .replace(/\s+/g, '-') // Replace spaces with hyphens
- .replace(/-+/g, '-') // Remove multiple consecutive hyphens
- .replace(/^-|-$/g, '') // Remove leading/trailing hyphens
- .trim();
- }
- window.simpleDebug = function() {
- console.log(`๐ SIMPLE DEBUG:`);
- console.log(`================`);
- if (appState.furnitureMode) {
- console.log(`In furniture mode: YES`);
- console.log(`Current collection: "${appState.selectedCollection?.name}"`);
- console.log(`Stored original collection: "${appState.selectedCollection?.originalCollectionName}"`);
- console.log(`Current pattern: "${appState.currentPattern?.name}"`);
- if (appState.selectedCollection?.originalCollectionName) {
- const slug = createPatternSlug(appState.currentPattern?.name || "test");
- console.log(`โ Path should be: data/furniture/sofa-capitol/patterns/${appState.selectedCollection.originalCollectionName}/${slug}/`);
- } else {
- console.log(`โ No original collection name stored!`);
- }
- } else {
- console.log(`In furniture mode: NO`);
- console.log(`Current collection: "${appState.selectedCollection?.name}"`);
- }
- };
- // Quick fix function:
- window.quickFix = function() {
- if (appState.furnitureMode && !appState.selectedCollection?.originalCollectionName) {
- // Try to guess the original collection from the furniture collection name
- const furnitureCollectionName = appState.selectedCollection?.name;
- if (furnitureCollectionName && furnitureCollectionName.includes("BOTANICAL")) {
- appState.selectedCollection.originalCollectionName = "botanicals";
- console.log(`๐ง Quick fix: Set original collection to "botanicals"`);
- return true;
- }
- }
- return false;
- };
- window.fixPatternPaths = function() {
- if (appState.furnitureMode && appState.currentPattern) {
- const originalCollectionName = appState.selectedCollection.originalCollectionName;
- const furnitureConfig = appState.selectedCollection.furnitureConfig;
- console.log(`๐ง Regenerating pattern paths:`);
- console.log(` Collection: ${originalCollectionName}`);
- console.log(` Pattern: ${appState.currentPattern.name}`);
- // Re-create the furniture pattern with correct paths
- const correctedPattern = createFurniturePattern(
- appState.currentPattern.originalPattern || appState.currentPattern,
- furnitureConfig,
- originalCollectionName
- );
- // Update the current pattern
- appState.currentPattern = correctedPattern;
- // Update in the collection too
- const patternIndex = appState.selectedCollection.patterns.findIndex(p => p.id === correctedPattern.id);
- if (patternIndex !== -1) {
- appState.selectedCollection.patterns[patternIndex] = correctedPattern;
- }
- console.log(`โ Pattern paths regenerated`);
- return correctedPattern;
- }
- };
- // Cache for furniture compatibility checks to improve performance
- let furnitureCompatibilityCache = new Map();
- let addFurnitureButtonDebounce = null;
- function getCompatibleFurniture(collectionName) {
- // Check cache first to avoid repeated computations
- if (furnitureCompatibilityCache.has(collectionName)) {
- return furnitureCompatibilityCache.get(collectionName);
- }
- // Reduced logging for performance
- if (window.COLORFLEX_DEBUG) {
- console.log(`๐ช Checking furniture compatibility for collection: ${collectionName}`);
- }
- if (!furnitureConfig) {
- // Don't spam the console - only warn once per collection
- if (!furnitureCompatibilityCache.has(collectionName + '_warned')) {
- console.warn("Furniture config not loaded yet");
- furnitureCompatibilityCache.set(collectionName + '_warned', true);
- }
- return [];
- }
- const compatible = Object.entries(furnitureConfig)
- .filter(([furnitureId, config]) => {
- const isCompatible = config.compatibleCollections &&
- config.compatibleCollections.includes(collectionName);
- return isCompatible;
- })
- .map(([furnitureId, config]) => ({
- id: furnitureId,
- name: config.name,
- thumbnail: config.thumbnail,
- description: config.description || '',
- config: config
- }));
- // Cache the result for future use
- furnitureCompatibilityCache.set(collectionName, compatible);
- if (window.COLORFLEX_DEBUG) {
- console.log(`Found ${compatible.length} compatible furniture pieces`);
- }
- return compatible;
- }
- function addTryFurnitureButtonDebounced() {
- // Debounce to prevent excessive calls
- if (addFurnitureButtonDebounce) {
- clearTimeout(addFurnitureButtonDebounce);
- }
- addFurnitureButtonDebounce = setTimeout(() => {
- addTryFurnitureButtonInternal();
- }, 100); // 100ms delay
- }
- // Legacy function name for backward compatibility
- function addTryFurnitureButton() {
- addTryFurnitureButtonDebounced();
- }
- function addTryFurnitureButtonInternal() {
- // Performance optimization - avoid excessive logging unless in debug mode
- if (window.COLORFLEX_DEBUG) {
- console.log("๐ช Adding Try Fabric button");
- }
- // Remove existing button if present
- const existingButton = document.getElementById('tryFurnitureBtn');
- if (existingButton) {
- existingButton.remove();
- }
- // Check compatibility
- const currentCollection = appState.selectedCollection?.name;
- if (!currentCollection) {
- if (window.COLORFLEX_DEBUG) {
- console.log("No current collection, skipping furniture button");
- }
- return;
- }
- const compatibleFurniture = getCompatibleFurniture(currentCollection);
- if (compatibleFurniture.length === 0) {
- if (window.COLORFLEX_DEBUG) {
- console.log("No compatible furniture found for", currentCollection);
- }
- return;
- }
- // Create button
- const button = document.createElement('button');
- button.id = 'tryFurnitureBtn';
- button.className = 'try-furniture-btn';
- button.innerHTML = `
- <span class="furniture-icon">๐ช</span>
- <span class="button-text">Try Fabric (${compatibleFurniture.length})</span>
- `;
- // Add styles
- button.style.cssText = `
- position: absolute;
- bottom: 10px;
- right: 10px;
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
- color: white;
- border: none;
- padding: 12px 18px;
- border-radius: 25px;
- font-family: 'Special Elite', monospace;
- font-size: 14px;
- font-weight: bold;
- cursor: pointer;
- box-shadow: 0 4px 15px rgba(0,0,0,0.2);
- transition: all 0.3s ease;
- display: flex;
- align-items: center;
- gap: 8px;
- z-index: 100;
- `;
- // Add hover effects
- button.addEventListener('mouseenter', () => {
- button.style.transform = 'translateY(-2px)';
- button.style.boxShadow = '0 6px 20px rgba(0,0,0,0.3)';
- });
- button.addEventListener('mouseleave', () => {
- button.style.transform = 'translateY(0)';
- button.style.boxShadow = '0 4px 15px rgba(0,0,0,0.2)';
- });
- // Add click handler
- button.addEventListener('click', () => {
- showFurnitureModal(compatibleFurniture);
- });
- // Find the room mockup container and add button
- const roomMockup = document.getElementById('roomMockup');
- if (roomMockup) {
- // Make sure the container is positioned relatively
- if (getComputedStyle(roomMockup).position === 'static') {
- roomMockup.style.position = 'relative';
- }
- roomMockup.appendChild(button);
- console.log("โ Try Furniture button added to room mockup");
- } else {
- console.error("โ Could not find room mockup container");
- }
- }
- // 3. showFurnitureModal function (also referenced but missing)
- function showFurnitureModal(compatibleFurniture) {
- console.log("๐ช Showing furniture modal with", compatibleFurniture.length, "options");
- // Remove existing modal
- const existingModal = document.getElementById('furnitureModal');
- if (existingModal) {
- existingModal.remove();
- }
- // Create modal overlay
- const modalOverlay = document.createElement('div');
- modalOverlay.id = 'furnitureModal';
- modalOverlay.style.cssText = `
- position: fixed;
- top: 0;
- left: 0;
- width: 100%;
- height: 100%;
- background: rgba(0, 0, 0, 0.7);
- display: flex;
- justify-content: center;
- align-items: center;
- z-index: 1000;
- animation: fadeIn 0.3s ease;
- `;
- // Create modal content
- const modalContent = document.createElement('div');
- modalContent.style.cssText = `
- background: white;
- border-radius: 15px;
- padding: 30px;
- max-width: 600px;
- width: 90%;
- max-height: 80%;
- overflow-y: auto;
- box-shadow: 0 20px 60px rgba(0,0,0,0.3);
- animation: slideUp 0.3s ease;
- `;
- // Modal header
- const header = document.createElement('div');
- header.innerHTML = `
- <h2 style="margin: 0 0 20px 0; font-family: 'Special Elite', monospace; color: #333; text-align: center;">
- Choose Furniture for ${toInitialCaps(appState.selectedCollection.name)}
- </h2>
- `;
- // Furniture grid
- const furnitureGrid = document.createElement('div');
- furnitureGrid.style.cssText = `
- display: grid;
- grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
- gap: 20px;
- margin-bottom: 20px;
- `;
- // Add furniture options
- compatibleFurniture.forEach(furniture => {
- const furnitureCard = document.createElement('div');
- furnitureCard.style.cssText = `
- border: 2px solid #e0e0e0;
- border-radius: 10px;
- padding: 15px;
- text-align: center;
- cursor: pointer;
- transition: all 0.3s ease;
- background: white;
- `;
- furnitureCard.innerHTML = `
- <img src="${normalizePath(furniture.thumbnail)}" alt="${furniture.name}"
- style="width: 100%; height: 120px; object-fit: cover; border-radius: 8px; margin-bottom: 10px;"
- onerror="this.style.background='#f0f0f0'; this.style.display='flex'; this.style.alignItems='center'; this.style.justifyContent='center'; this.innerHTML='๐ช';">
- <h3 style="margin: 10px 0 5px 0; font-family: 'Special Elite', monospace; font-size: 16px;">${furniture.name}</h3>
- <p style="margin: 0; font-size: 12px; color: #666; line-height: 1.4;">${furniture.description}</p>
- `;
- // Hover effects
- furnitureCard.addEventListener('mouseenter', () => {
- furnitureCard.style.borderColor = '#667eea';
- furnitureCard.style.transform = 'translateY(-2px)';
- furnitureCard.style.boxShadow = '0 8px 25px rgba(0,0,0,0.1)';
- });
- furnitureCard.addEventListener('mouseleave', () => {
- furnitureCard.style.borderColor = '#e0e0e0';
- furnitureCard.style.transform = 'translateY(0)';
- furnitureCard.style.boxShadow = 'none';
- });
- // Click handler
- furnitureCard.addEventListener('click', () => {
- selectFurniture(furniture);
- modalOverlay.remove();
- });
- furnitureGrid.appendChild(furnitureCard);
- });
- // Cancel button
- const cancelButton = document.createElement('button');
- cancelButton.textContent = 'Cancel';
- cancelButton.style.cssText = `
- background: #ccc;
- color: #333;
- border: none;
- padding: 10px 20px;
- border-radius: 5px;
- cursor: pointer;
- font-family: 'Special Elite', monospace;
- display: block;
- margin: 0 auto;
- `;
- cancelButton.addEventListener('click', () => {
- modalOverlay.remove();
- });
- // Assemble modal
- modalContent.appendChild(header);
- modalContent.appendChild(furnitureGrid);
- modalContent.appendChild(cancelButton);
- modalOverlay.appendChild(modalContent);
- // Close on overlay click
- modalOverlay.addEventListener('click', (e) => {
- if (e.target === modalOverlay) {
- modalOverlay.remove();
- }
- });
- // Add CSS animations
- const style = document.createElement('style');
- style.textContent = `
- @keyframes fadeIn {
- from { opacity: 0; }
- to { opacity: 1; }
- }
- @keyframes slideUp {
- from { transform: translateY(50px); opacity: 0; }
- to { transform: translateY(0); opacity: 1; }
- }
- `;
- document.head.appendChild(style);
- document.body.appendChild(modalOverlay);
- }
- // 4. selectFurniture function
- function selectFurniture(selectedFurniture) {
- console.log("๐ช Selected furniture:", selectedFurniture.name);
- console.log("๐งต Full furniture object:", selectedFurniture);
- // Store selected furniture in appState
- appState.selectedFurniture = selectedFurniture;
- appState.isInFabricMode = selectedFurniture.name === "Fabric";
- // Direct check for fabric name
- if (selectedFurniture.name === "Fabric") {
- console.log("๐งต ================================");
- console.log("๐งต FABRIC NAME DETECTED - CALLING FABRIC MOCKUP");
- console.log("๐งต ================================");
- renderFabricMockup();
- return;
- }
- // Switch to furniture mode for actual furniture
- console.log("๐ช Regular furniture selected, switching to furniture mode");
- switchToFurnitureMode(selectedFurniture);
- }
- // 5. addBackToPatternsButton function
- function addBackToPatternsButton() {
- console.log("๐ addBackToPatternsButton() called");
- const existingButton = document.getElementById('backToPatternsBtn');
- if (existingButton) {
- console.log("๐๏ธ Removing existing back button");
- existingButton.remove();
- }
- const button = document.createElement('button');
- button.id = 'backToPatternsBtn';
- button.innerHTML = `
- <span>โ Back to Patterns</span>
- `;
- button.style.cssText = `
- position: absolute;
- bottom: 10px;
- left: 10px;
- background: linear-gradient(135deg, #ff7b7b 0%, #667eea 100%);
- color: white;
- border: none;
- padding: 12px 18px;
- border-radius: 25px;
- font-family: 'Special Elite', monospace;
- font-size: 14px;
- font-weight: bold;
- cursor: pointer;
- box-shadow: 0 4px 15px rgba(0,0,0,0.2);
- transition: all 0.3s ease;
- z-index: 100;
- `;
- console.log("๐ Adding click event listener to back button");
- button.addEventListener('click', (event) => {
- console.log("๐ Back button clicked!");
- event.stopPropagation(); // Prevent zoom handler from receiving this event
- event.preventDefault(); // Prevent any default behavior
- // Check if we're in fabric mode or furniture mode
- if (appState.isInFabricMode) {
- console.log("๐งต Returning from fabric mode to patterns");
- returnToPatternsModeFromFabric();
- } else {
- console.log("๐ช Returning from furniture mode to patterns");
- returnToPatternsMode();
- }
- });
- const roomMockup = document.getElementById('roomMockup');
- if (roomMockup) {
- roomMockup.appendChild(button);
- console.log("โ Back button added to DOM");
- // Test if button is actually clickable
- console.log("๐งช Button in DOM:", document.getElementById('backToPatternsBtn'));
- console.log("๐งช Button parent:", document.getElementById('backToPatternsBtn')?.parentElement);
- } else {
- console.error("โ roomMockup not found!");
- }
- }
- // Function to return from fabric mode to patterns mode
- function returnToPatternsModeFromFabric() {
- console.log("๐งต Returning from fabric mode to patterns");
- // Clear fabric mode state
- appState.selectedFurniture = null;
- appState.isInFabricMode = false;
- // Remove back button
- const backButton = document.getElementById('backToPatternsBtn');
- if (backButton) {
- backButton.remove();
- }
- // Remove fabric tuning controls
- removeFabricTuningControls();
- // Re-add try furniture button
- addTryFurnitureButton();
- // Trigger room mockup update to show regular pattern view
- if (appState.currentPattern) {
- updateRoomMockup();
- }
- console.log("โ Returned from fabric mode to patterns mode");
- }
- // 6. initializeTryFurnitureFeature function
- function initializeTryFurnitureFeature() {
- console.log("๐ช Initializing Try Furniture feature");
- // Add the button when a collection is loaded
- if (appState.selectedCollection && !appState.furnitureMode) {
- addTryFurnitureButton();
- }
- }
- // Resolve furniture pattern paths using collection-based structure
- function resolveFurniturePatternPaths(furnitureConfig, collectionName, patternName, originalPatternLayers) {
- console.log(`๐ Resolving furniture pattern paths:`);
- console.log(` Collection: "${collectionName}"`);
- console.log(` Pattern: "${patternName}"`);
- // โ VALIDATION: Make sure we have a valid collection name
- if (!collectionName || collectionName === "UNKNOWN" || collectionName === patternName) {
- console.error(`โ Invalid collection name: "${collectionName}"`);
- console.error(` Pattern name: "${patternName}"`);
- console.error(` These should be different!`);
- // Try to get it from the current furniture collection
- const fallbackCollectionName = appState.selectedCollection?.originalCollectionName;
- if (fallbackCollectionName) {
- console.log(`๐ง Using fallback collection name: "${fallbackCollectionName}"`);
- collectionName = fallbackCollectionName;
- } else {
- console.error(`โ No fallback collection name available!`);
- return [];
- }
- }
- const patternSlug = createPatternSlug(patternName);
- // Replace template variables
- const patternFolder = furnitureConfig.patternPathTemplate
- .replace('{collection}', collectionName)
- .replace('{patternSlug}', patternSlug);
- console.log(` Pattern slug: "${patternSlug}"`);
- console.log(` โ Final folder: "${patternFolder}"`);
- // Map layers to furniture paths
- const furniturePatternLayers = originalPatternLayers.map((layer, index) => {
- const originalFileName = layer.path.split('/').pop();
- const layerName = originalFileName.replace(/\.[^/.]+$/, '');
- const cleanLayerName = layerName.replace(/^[^_]*_/, ''); // Remove everything before first underscore
- const furnitureFileName = `${patternSlug}_${cleanLayerName}.png`;
- const furniturePath = `${patternFolder}${furnitureFileName}`;
- return {
- ...layer,
- path: furniturePath,
- originalPath: layer.path,
- furnitureFileName: furnitureFileName
- };
- });
- return furniturePatternLayers;
- }
- function createFurniturePattern(originalPattern, furnitureConfig, collectionName) {
- console.log(`๐ Creating furniture pattern:`);
- console.log(` Pattern: ${originalPattern.name}`);
- console.log(` Collection: ${collectionName}`);
- console.log(` Furniture: ${furnitureConfig.name}`);
- // โ VERIFY: Make sure collectionName is correct
- if (!collectionName || collectionName === originalPattern.name) {
- console.error(`โ COLLECTION NAME ERROR!`);
- console.error(` Expected collection name like "botanicals"`);
- console.error(` Got: "${collectionName}"`);
- console.error(` Pattern name: "${originalPattern.name}"`);
- console.error(` These should be different!`);
- }
- const furniturePatternLayers = resolveFurniturePatternPaths(
- furnitureConfig,
- collectionName, // โ This should be "botanicals"
- originalPattern.name, // โ This should be "Key Largo"
- originalPattern.layers || []
- );
- const furniturePattern = {
- ...originalPattern,
- layers: furniturePatternLayers,
- isFurniture: true,
- furnitureConfig: furnitureConfig,
- originalPattern: originalPattern,
- collectionName: collectionName // Store collection name for reference
- };
- console.log(`โ Created furniture pattern with ${furniturePatternLayers.length} layers`);
- console.log(` Expected path pattern: data/furniture/.../patterns/${collectionName}/${createPatternSlug(originalPattern.name)}/`);
- return furniturePattern;
- }
- // Updated switchToFurnitureMode function
- function switchToFurnitureMode(furniture) {
- console.log("๐ Switching to furniture mode for:", furniture.name);
- // โ SIMPLE: Just grab the current collection name RIGHT NOW
- const originalCollectionName = appState.selectedCollection.name;
- console.log(`๐ Original collection name: "${originalCollectionName}"`);
- // Store the ENTIRE original collection
- appState.originalCollection = { ...appState.selectedCollection };
- // Convert all patterns to furniture patterns using the CURRENT collection name
- const furniturePatterns = appState.selectedCollection.patterns.map(pattern => {
- return createFurniturePattern(pattern, furniture.config, originalCollectionName);
- });
- // Create virtual furniture collection
- const furnitureCollection = {
- name: `${originalCollectionName.toUpperCase()} ${furniture.name.toUpperCase()}`,
- patterns: furniturePatterns,
- curatedColors: appState.selectedCollection.curatedColors,
- coordinates: [],
- mockup: null,
- furnitureType: furniture.id,
- wallMask: furniture.config.wallMask || "default-wall-mask.png", // โ Ensure it's not null
- // โ SIMPLE: Store the original collection name directly
- originalCollectionName: originalCollectionName,
- furnitureConfig: furniture.config
- };
- // Update app state
- appState.selectedCollection = furnitureCollection;
- appState.furnitureMode = true;
- console.log(`โ Switched to furniture mode. Paths will use: "${originalCollectionName}"`);
- // Update UI
- if (dom.collectionHeader) {
- dom.collectionHeader.textContent = furnitureCollection.name;
- }
- // Remove try furniture button and add back button
- const tryButton = document.getElementById('tryFurnitureBtn');
- if (tryButton) tryButton.remove();
- addBackToPatternsButton();
- // Trigger re-render
- if (appState.currentPattern) {
- const furniturePattern = furniturePatterns.find(p => p.id === appState.currentPattern.id);
- if (furniturePattern) {
- loadPatternData(appState.selectedCollection, furniturePattern.id);
- }
- }
- }
- function returnToPatternsMode() {
- console.log("๐ Returning to patterns mode");
- // Restore original collection
- if (appState.originalCollection) {
- console.log("๐ Restoring original collection:", appState.originalCollection.name);
- appState.selectedCollection = appState.originalCollection; // Remove .fullCollection
- appState.furnitureMode = false;
- appState.originalCollection = null;
- // Clear fabric mode state
- appState.selectedFurniture = null;
- appState.isInFabricMode = false;
- // Update UI
- if (dom.collectionHeader) {
- dom.collectionHeader.textContent = toInitialCaps(appState.selectedCollection.name);
- }
- // Remove back button
- const backButton = document.getElementById('backToPatternsBtn');
- if (backButton) {
- backButton.remove();
- }
- // Re-add try furniture button
- addTryFurnitureButton();
- // Trigger re-render in patterns mode
- if (appState.currentPattern) {
- // Find the original pattern (not the furniture version)
- const originalPattern = appState.selectedCollection.patterns.find(p => p.id === appState.currentPattern.id);
- if (originalPattern) {
- loadPatternData(appState.selectedCollection, originalPattern.id);
- }
- }
- console.log("โ Returned to patterns mode");
- } else {
- console.error("โ Cannot return to patterns mode - original collection not found");
- }
- }
- // Development helper: Generate expected folder structure
- function generateFolderStructure(collectionName, furnitureId) {
- const collection = appState.collections?.find(c => c.name === collectionName);
- const furniture = furnitureConfig?.[furnitureId];
- if (!collection || !furniture) {
- console.error("โ Collection or furniture not found");
- return;
- }
- console.log(`๐ FOLDER STRUCTURE for ${furniture.name} + ${collectionName}:`);
- console.log(`๐ Base path: data/furniture/${furnitureId}/patterns/${collectionName}/`);
- console.log(`๐ Folders needed:`);
- const folders = [];
- collection.patterns.forEach(pattern => {
- const slug = createPatternSlug(pattern.name);
- const folder = `data/furniture/${furnitureId}/patterns/${collectionName}/${slug}/`;
- folders.push({
- pattern: pattern.name,
- slug: slug,
- folder: folder
- });
- console.log(` ${folder}`);
- });
- console.log(`๐ Total folders needed: ${folders.length}`);
- return folders;
- }
- // Development helper: Check what files are expected for a pattern
- function getExpectedFiles(collectionName, patternName, furnitureId) {
- const collection = appState.collections?.find(c => c.name === collectionName);
- const pattern = collection?.patterns.find(p => p.name === patternName);
- const furniture = furnitureConfig?.[furnitureId];
- if (!pattern || !furniture) {
- console.error("โ Pattern or furniture not found");
- return;
- }
- const slug = createPatternSlug(patternName);
- const folder = `https://so-animation.com/colorflex/data/furniture/${furnitureId}/patterns/${collectionName}/${slug}/`;
- console.log(`๐ EXPECTED FILES for ${patternName} on ${furniture.name}:`);
- console.log(`๐ Folder: ${folder}`);
- console.log(`๐ Files needed:`);
- const expectedFiles = [];
- if (pattern.layers) {
- pattern.layers.forEach((layer, index) => {
- const originalFileName = layer.path.split('/').pop();
- const layerName = originalFileName.replace(/\.[^/.]+$/, '');
- const furnitureFileName = `${slug}-${layerName}.png`;
- expectedFiles.push({
- original: originalFileName,
- furniture: furnitureFileName,
- fullPath: `${folder}${furnitureFileName}`
- });
- console.log(` ${furnitureFileName}`);
- });
- }
- return {
- folder: folder,
- files: expectedFiles
- };
- }
- // 1. Console commands for planning your work
- window.workflowHelpers = {
- // See all expected folders for a furniture + collection combo
- showFolders: function(furnitureId, collectionName) {
- console.log(`๐ FOLDER STRUCTURE: ${furnitureId} + ${collectionName}`);
- return generateFolderStructure(collectionName, furnitureId);
- },
- // See expected files for a specific pattern
- showFiles: function(collectionName, patternName, furnitureId) {
- console.log(`๐ EXPECTED FILES: ${patternName} on ${furnitureId}`);
- return getExpectedFiles(collectionName, patternName, furnitureId);
- },
- // Get overview of all work needed
- showPlan: function() {
- console.log(`๐จ COMPLETE RENDERING PLAN`);
- return generateRenderingPlan();
- },
- // Test pattern slug generation
- testSlug: function(patternName) {
- const slug = createPatternSlug(patternName);
- console.log(`Pattern: "${patternName}" โ Slug: "${slug}"`);
- return slug;
- },
- // Check what's compatible
- showCompatibility: function() {
- console.log(`๐ FURNITURE COMPATIBILITY:`);
- Object.entries(furnitureConfig || {}).forEach(([furnitureId, furniture]) => {
- console.log(`${furniture.name}: ${furniture.compatibleCollections.join(', ')}`);
- });
- },
- // Generate folder creation script
- generateFolderScript: function(furnitureId) {
- const furniture = furnitureConfig?.[furnitureId];
- if (!furniture) {
- console.error(`โ Furniture ${furnitureId} not found`);
- return;
- }
- console.log(`๐ FOLDER CREATION SCRIPT for ${furniture.name}:`);
- console.log(`# Copy and paste these commands to create folders:\n`);
- let script = `# Furniture: ${furniture.name}\n`;
- script += `mkdir -p data/furniture/${furnitureId}/patterns\n\n`;
- furniture.compatibleCollections.forEach(collectionName => {
- const collection = appState.collections?.find(c => c.name === collectionName);
- if (!collection) return;
- script += `# Collection: ${collectionName}\n`;
- script += `mkdir -p data/furniture/${furnitureId}/patterns/${collectionName}\n`;
- collection.patterns.forEach(pattern => {
- const slug = createPatternSlug(pattern.name);
- script += `mkdir -p data/furniture/${furnitureId}/patterns/${collectionName}/${slug}\n`;
- });
- script += `\n`;
- });
- console.log(script);
- return script;
- }
- };
- // 2. Development status checker
- function checkFurnitureImplementationStatus() {
- console.log(`๐ FURNITURE IMPLEMENTATION STATUS CHECK:`);
- console.log(`======================================`);
- // Check if furniture config is loaded
- if (!furnitureConfig) {
- console.log(`โ furnitureConfig not loaded`);
- return;
- }
- console.log(`โ furnitureConfig loaded: ${Object.keys(furnitureConfig).length} furniture pieces`);
- // Check collections
- if (!appState.collections || appState.collections.length === 0) {
- console.log(`โ Collections not loaded`);
- return;
- }
- console.log(`โ Collections loaded: ${appState.collections.length} collections`);
- // Check current state
- const currentCollection = appState.selectedCollection?.name;
- if (!currentCollection) {
- console.log(`โ No collection currently selected`);
- return;
- }
- console.log(`โ Current collection: ${currentCollection}`);
- // Check compatibility
- const compatible = getCompatibleFurniture(currentCollection);
- console.log(`โ Compatible furniture: ${compatible.length} pieces`);
- compatible.forEach(f => console.log(` - ${f.name}`));
- // Check if Try Furniture button should be visible
- const tryButton = document.getElementById('tryFurnitureBtn');
- const backButton = document.getElementById('backToPatternsBtn');
- if (appState.furnitureMode) {
- console.log(`๐ช Currently in FURNITURE MODE`);
- console.log(` Back button present: ${!!backButton}`);
- } else {
- console.log(`๐จ Currently in PATTERN MODE`);
- console.log(` Try Furniture button present: ${!!tryButton}`);
- if (!tryButton && compatible.length > 0) {
- console.log(`โ ๏ธ Try Furniture button should be visible but isn't!`);
- }
- }
- return {
- furnitureConfigLoaded: !!furnitureConfig,
- collectionsLoaded: appState.collections?.length > 0,
- currentCollection: currentCollection,
- compatibleFurniture: compatible.length,
- furnitureMode: appState.furnitureMode,
- tryButtonPresent: !!tryButton,
- backButtonPresent: !!backButton
- };
- }
- // 3. Easy console commands
- window.checkStatus = checkFurnitureImplementationStatus;
- // 4. Example usage guide
- // Workflow helpers available in development mode only
- if (window.location.hostname === 'localhost' || window.location.hostname.includes('dev')) {
- console.log(`
- ๐ช FURNITURE WORKFLOW HELPERS LOADED!
- =====================================
- Console Commands:
- โข workflowHelpers.showPlan() - See complete rendering plan
- โข workflowHelpers.showFolders('sofa-capitol', 'botanicals') - See folder structure
- โข workflowHelpers.showFiles('botanicals', 'Key Largo', 'sofa-capitol') - See expected files
- โข workflowHelpers.testSlug('Pattern Name Here') - Test slug conversion
- โข workflowHelpers.showCompatibility() - See what's compatible with what
- โข workflowHelpers.generateFolderScript('sofa-capitol') - Generate mkdir commands
- โข checkStatus() - Check implementation status
- Example Workflow:
- 1. workflowHelpers.showPlan() - See total work needed
- 2. workflowHelpers.generateFolderScript('sofa-capitol') - Create folders
- 3. Render patterns and save to generated folders
- 4. Test with Try Furniture button!
- `);
- }
- // 5. Integration check
- document.addEventListener('DOMContentLoaded', () => {
- // Wait a bit for everything to load
- setTimeout(() => {
- console.log(`๐ Running furniture integration check...`);
- checkFurnitureImplementationStatus();
- }, 2000);
- });
- // Load furniture config on app init
- let furnitureConfig = null;
- async function loadFurnitureConfig() {
- try {
- console.log("๐ Loading furniture config...");
- let response;
- const furnitureConfigUrl = window.ColorFlexAssets?.furnitureConfigUrl || '/assets/furniture-config.json';
- response = await fetch(furnitureConfigUrl, {
- method: 'GET',
- cache: 'no-cache',
- headers: {
- 'Content-Type': 'application/json',
- }
- });
- if (response.ok) {
- furnitureConfig = await response.json();
- console.log('โ Loaded furniture config:', furnitureConfig);
- // Debug the structure
- Object.keys(furnitureConfig).forEach(key => {
- console.log(` ${key}:`, Object.keys(furnitureConfig[key]));
- });
- } else {
- if (response.status === 0 || response.status === 403) {
- throw new Error('CORS Error: Cross-origin request blocked');
- }
- console.error("โ Furniture config response not ok:", response.status);
- }
- } catch (e) {
- if (e.name === 'TypeError' && e.message.includes('fetch')) {
- console.error('โ Network/CORS Error loading furniture config:', e);
- } else {
- console.error("โ Error loading furniture config:", e);
- }
- }
- }
- dom._patternName = document.getElementById("patternName"); // Initial assignment
- // Fetch colors from colors.json
- async function loadColors() {
- try {
- // Check if colors are embedded (Shopify mode)
- if (window.ColorFlexData && window.ColorFlexData.colors) {
- console.log("๐ฏ Using embedded Sherwin-Williams colors");
- appState.colorsData = window.ColorFlexData.colors;
- console.log("โ Colors loaded:", appState.colorsData.length);
- return;
- }
- // Load directly from Shopify assets
- console.log("๐ Loading colors from Shopify assets");
- const colorsUrl = window.ColorFlexAssets?.colorsUrl || "/assets/colors.json";
- const response = await fetch(colorsUrl, {
- method: 'GET',
- cache: 'no-cache',
- headers: {
- 'Content-Type': 'application/json',
- }
- });
- if (!response.ok) {
- if (response.status === 0 || response.status === 403) {
- throw new Error('CORS Error: Cross-origin request blocked');
- }
- throw new Error(`HTTP error: ${response.status}`);
- }
- const data = await response.json();
- if (!Array.isArray(data) || data.length === 0) {
- throw new Error("Colors data is empty or invalid");
- }
- appState.colorsData = data;
- console.log("โ Colors loaded:", appState.colorsData.length);
- } catch (err) {
- console.error("รขยล Error loading colors:", err);
- alert("Failed to load Sherwin-Williams colors.");
- }
- }
- // Lookup color from colors.json data
- let lookupColor = (colorName) => {
- if (!colorName || typeof colorName !== "string") {
- console.warn(`Invalid colorName: ${colorName}, defaulting to #FFFFFF`);
- return "#FFFFFF";
- }
- const cleanedColorName = colorName.replace(/^(SW|SC)\d+\s*/i, "").toLowerCase().trim();
- console.log(`lookupColor: cleanedColorName=${cleanedColorName}`);
- if (/^#[0-9A-F]{6}$/i.test(cleanedColorName)) {
- console.log(`lookupColor: ${colorName} is a hex value, returning ${cleanedColorName}`);
- return cleanedColorName;
- }
- const colorEntry = appState.colorsData.find(c => c.color_name.toLowerCase() === cleanedColorName);
- if (!colorEntry) {
- console.warn(`Color '${cleanedColorName}' not found in colorsData, defaulting to #FFFFFF`);
- return "#FFFFFF";
- }
- console.log(`Looked up ${colorName} -> #${colorEntry.hex}`);
- return `#${colorEntry.hex}`;
- };
- if (USE_GUARD && DEBUG_TRACE) {
- lookupColor = guard(traceWrapper(lookupColor, "lookupColor")); // Wrapped for debugging
- } else if (USE_GUARD) {
- lookupColor = guard(lookupColor, "lookupColor"); // Wrapped for debugging
- }
- // Hamburger menu functionality
- document.addEventListener('DOMContentLoaded', function() {
- const hamburgerBtn = document.getElementById('hamburgerBtn');
- const sidebar = document.getElementById('leftSidebar');
- if (hamburgerBtn && sidebar) {
- hamburgerBtn.addEventListener('click', function() {
- hamburgerBtn.classList.toggle('active');
- sidebar.classList.toggle('open');
- });
- // Close sidebar when clicking outside on mobile
- document.addEventListener('click', function(e) {
- if (window.innerWidth <= 1023 &&
- !sidebar.contains(e.target) &&
- !hamburgerBtn.contains(e.target) &&
- sidebar.classList.contains('open')) {
- hamburgerBtn.classList.remove('active');
- sidebar.classList.remove('open');
- }
- });
- }
- });
- // Check if a specific pattern has furniture renders
- async function checkFurnitureAvailability(patternName) {
- const patternSlug = patternName.toLowerCase().replace(/ /g, '-');
- const manifestUrl = `data/furniture/sofa-capitol/patterns/${patternSlug}/manifest.json`;
- try {
- const response = await fetch(manifestUrl, {
- method: 'GET',
- mode: 'cors',
- cache: 'no-cache',
- headers: {
- 'Content-Type': 'application/json',
- }
- });
- if (response.ok) {
- const manifest = await response.json();
- return {
- available: true,
- manifest: manifest,
- furnitureType: 'sofa-capitol'
- };
- }
- } catch (e) {
- // No furniture version
- }
- return { available: false };
- }
- // Call loadFurnitureConfig when your app initializes
- loadFurnitureConfig();
- // Utility Functions
- // Helper function for scaling
- function scaleToFit(img, targetWidth, targetHeight) {
- const aspectRatio = img.width / img.height;
- let drawWidth = targetWidth;
- let drawHeight = targetHeight;
- if (aspectRatio > targetWidth / targetHeight) {
- drawHeight = drawWidth / aspectRatio;
- } else {
- drawWidth = drawHeight * aspectRatio;
- }
- const x = (targetWidth - drawWidth) / 2;
- const y = (targetHeight - drawHeight) / 2;
- return { width: drawWidth, height: drawHeight, x, y };
- }
- // Shared helper for loading and tinting a masked image
- async function drawMaskedLayer(imgPath, tintColor, label) {
- // Check if this is a wall panel image
- const isWallPanel = imgPath.includes('wall-panels');
- // Get the original, untinted grayscale image for alpha calculation
- const originalUrl = await new Promise(resolve =>
- processImage(imgPath, resolve, null, 2.2, false, false, false)
- );
- const img = await loadImage(originalUrl);
- // Draw the original image centered on an offscreen canvas
- const offscreen = document.createElement("canvas");
- offscreen.width = 1080;
- offscreen.height = 1080;
- const offCtx = offscreen.getContext("2d");
- drawCenteredImage(offCtx, img, 1080, 1080);
- // Get pixel data
- let imageData;
- try {
- imageData = offCtx.getImageData(0, 0, 1080, 1080);
- } catch (e) {
- console.warn("โ ๏ธ Canvas tainted, skipping masked layer processing:", e.message);
- return;
- }
- const { data } = imageData;
- // Invert luminance for alpha: white (255) รขโ โ alpha 0, black (0) รขโ โ alpha 255
- for (let i = 0; i < data.length; i += 4) {
- const r = data[i];
- const g = data[i + 1];
- const b = data[i + 2];
- const luminance = 0.299 * r + 0.587 * g + 0.114 * b;
- data[i + 3] = 255 - luminance; // INVERTED for correct alpha
- }
- offCtx.putImageData(imageData, 0, 0);
- // Prepare the colored (tint) layer and mask it with the alpha
- const tintLayer = document.createElement("canvas");
- tintLayer.width = 1080;
- tintLayer.height = 1080;
- const tintCtx = tintLayer.getContext("2d");
- tintCtx.fillStyle = tintColor;
- tintCtx.fillRect(0, 0, 1080, 1080);
- tintCtx.globalCompositeOperation = "destination-in";
- tintCtx.drawImage(offscreen, 0, 0);
- // Composite result onto main canvas
- ctx.globalAlpha = 1.0;
- ctx.globalCompositeOperation = "source-over";
- ctx.drawImage(tintLayer, 0, 0);
- console.log(`โ [${label}] tint-mask drawn.`);
- }
- function applyNormalizationProcessing(data, rLayer, gLayer, bLayer) {
- // IMPROVED normalization logic for better detail preservation
- let minLuminance = 255, maxLuminance = 0;
- for (let i = 0; i < data.length; i += 4) {
- const luminance = 0.299 * data[i] + 0.587 * data[i + 1] + 0.114 * data[i + 2];
- minLuminance = Math.min(minLuminance, luminance);
- maxLuminance = Math.max(maxLuminance, luminance);
- }
- const range = maxLuminance - minLuminance || 1;
- console.log("Min Luminance:", minLuminance, "Max Luminance:", maxLuminance);
- for (let i = 0; i < data.length; i += 4) {
- const luminance = 0.299 * data[i] + 0.587 * data[i + 1] + 0.114 * data[i + 2];
- let normalized = (luminance - minLuminance) / range;
- normalized = Math.max(0, Math.min(1, normalized));
- let alpha = 1 - normalized;
- if (alpha > 0.8) {
- alpha = 1;
- } else if (alpha > 0.5) {
- alpha = 0.8 + (alpha - 0.5) * 0.67;
- } else if (alpha > 0.2) {
- alpha = alpha * 1.6;
- } else {
- alpha = alpha * 0.5;
- }
- alpha = Math.min(1, Math.max(0, alpha));
- if (alpha > 0.05) {
- data[i] = rLayer;
- data[i + 1] = gLayer;
- data[i + 2] = bLayer;
- } else {
- data[i] = 0;
- data[i + 1] = 0;
- data[i + 2] = 0;
- }
- data[i + 3] = Math.round(alpha * 255);
- }
- }
- function resolveColor(raw) {
- const color = (!raw || typeof raw !== "string") ? "Snowbound" : raw.trim().toUpperCase();
- const resolved = lookupColor(color);
- if (!resolved) console.warn(`รขลก รฏยธย [resolveColor] Could not resolve color: '${color}', using Snowbound`);
- return resolved || lookupColor("Snowbound") || "#DDDDDD";
- }
- function drawCenteredImage(ctx, img, canvasWidth, canvasHeight) {
- const aspect = img.width / img.height;
- let drawWidth = canvasWidth;
- let drawHeight = drawWidth / aspect;
- if (drawHeight > canvasHeight) {
- drawHeight = canvasHeight;
- drawWidth = drawHeight * aspect;
- }
- const offsetX = Math.round((canvasWidth - drawWidth) / 2);
- const offsetY = Math.round((canvasHeight - drawHeight) / 2);
- ctx.drawImage(img, offsetX, offsetY, drawWidth, drawHeight);
- }
- function hexToHSL(hex) {
- // Remove # if present
- hex = hex.replace(/^#/, '');
- // Convert 3-digit to 6-digit hex
- if (hex.length === 3) {
- hex = hex.split('').map(x => x + x).join('');
- }
- if (hex.length !== 6) {
- console.error("รขยล Invalid HEX color:", hex);
- return null;
- }
- const r = parseInt(hex.substr(0, 2), 16) / 255;
- const g = parseInt(hex.substr(2, 2), 16) / 255;
- const b = parseInt(hex.substr(4, 2), 16) / 255;
- const max = Math.max(r, g, b);
- const min = Math.min(r, g, b);
- let h, s, l = (max + min) / 2;
- if (max === min) {
- h = s = 0; // achromatic
- } else {
- const d = max - min;
- s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
- switch (max) {
- case r: h = ((g - b) / d + (g < b ? 6 : 0)); break;
- case g: h = ((b - r) / d + 2); break;
- case b: h = ((r - g) / d + 4); break;
- }
- h *= 60;
- }
- return {
- h: Math.round(h),
- s: Math.round(s * 100),
- l: Math.round(l * 100)
- };
- }
- function hslToHex(h, s, l) {
- s /= 100;
- l /= 100;
- const k = n => (n + h / 30) % 12;
- const a = s * Math.min(l, 1 - l);
- const f = n =>
- Math.round(255 * (l - a * Math.max(-1, Math.min(k(n) - 3, Math.min(9 - k(n), 1)))));
- return `#${[f(0), f(8), f(4)].map(x => x.toString(16).padStart(2, '0')).join('')}`;
- }
- function clamp(value, min, max) {
- return Math.min(max, Math.max(min, value));
- }
- function findClosestSWColor(targetHex) {
- let bestMatch = null;
- let bestDistance = Infinity;
- for (const color of colorsData) {
- const dist = colorDistance(`#${color.hex}`, targetHex);
- if (dist < bestDistance) {
- bestDistance = dist;
- bestMatch = color;
- }
- }
- return bestMatch;
- }
- function colorDistance(hex1, hex2) {
- const rgb1 = hexToRGB(hex1);
- const rgb2 = hexToRGB(hex2);
- return Math.sqrt(
- Math.pow(rgb1.r - rgb2.r, 2) +
- Math.pow(rgb1.g - rgb2.g, 2) +
- Math.pow(rgb1.b - rgb2.b, 2)
- );
- }
- function hexToRGB(hex) {
- hex = hex.replace(/^#/, "");
- if (hex.length === 3) hex = hex.split('').map(c => c + c).join('');
- const bigint = parseInt(hex, 16);
- return { r: (bigint >> 16) & 255, g: (bigint >> 8) & 255, b: (bigint & 255) };
- }
- // Reusable listener setup
- const setupPrintListener = () => {
- const tryAttachListener = (attempt = 1, maxAttempts = 10) => {
- const printButton = document.getElementById("printButton");
- console.log(`Print listener - Attempt ${attempt} - Looking for printButton: ${printButton ? "Found" : "Not found"}`);
- if (printButton) {
- const newButton = printButton.cloneNode(true);
- printButton.parentNode.replaceChild(newButton, printButton);
- newButton.addEventListener("click", async () => {
- console.log("Print preview triggered");
- const result = await generatePrintPreview();
- if (!result) {
- console.error("Print preview - Failed to generate output");
- }
- });
- console.log("Print listener attached");
- } else if (attempt < maxAttempts) {
- console.warn(`Print button not found, retrying (${attempt}/${maxAttempts})`);
- setTimeout(() => tryAttachListener(attempt + 1, maxAttempts), 500);
- } else {
- console.error("Print button not found after max attempts");
- }
- };
- console.log("Print listener - Initial DOM state:", document.readyState);
- console.log("Print listener - Pattern preview wrapper:", document.getElementById("patternPreviewWrapper"));
- if (document.readyState === "complete" || document.readyState === "interactive") {
- tryAttachListener();
- } else {
- document.addEventListener("DOMContentLoaded", () => {
- console.log("Print listener - DOMContentLoaded fired");
- tryAttachListener();
- });
- }
- };
- const toInitialCaps = (str) =>
- str
- .toLowerCase()
- .replace(/\.\w+$/, '') // Remove file extensions like .jpg, .png, etc.
- .replace(/-\d+x\d+$|-variant$/i, '') // Remove suffixes like -24x24, -variant
- .replace(/_/g, ' ') // Replace underscores with spaces
- .split(/[\s-]+/) // Split on spaces and hyphens
- .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
- .join(" ");
- const stripSWNumber = (colorName) => {
- return colorName.replace(/(SW|SC)\d+\s*/, '').trim(); // Removes "SW" followed by digits and optional space
- };
- const getContrastClass = (hex) => {
- // console.trace("getContrastClass received:", hex);
- if (typeof hex !== "string" || !hex.startsWith("#") || hex.length < 7) {
- console.warn("โ ๏ธ Invalid hex value in getContrastClass:", hex);
- return "text-black"; // or choose a safe default
- }
- const r = parseInt(hex.slice(1, 3), 16);
- const g = parseInt(hex.slice(3, 5), 16);
- const b = parseInt(hex.slice(5, 7), 16);
- const brightness = (r * 299 + g * 587 + b * 114) / 1000;
- return brightness > 128 ? "text-black" : "text-white";
- };
- async function drawFurnitureLayer(ctx, imagePath, options = {}) {
- console.log("๐ drawFurnitureLayer ENTRY:");
- console.log(" imagePath received:", imagePath);
- console.log(" Is sofa base?", imagePath?.includes('sofa-capitol-base'));
- console.log(" Is ferns pattern?", imagePath?.includes('ferns'));
- const {
- tintColor = null,
- isMask = false,
- opacity = 1.0,
- blendMode = "source-over",
- zoomState = null,
- highRes = false
- } = options;
- const width = 600;
- const height = 450;
- // โ Scale up for high resolution pattern rendering
- const renderScale = highRes ? 2 : 1;
- const renderWidth = width * renderScale;
- const renderHeight = height * renderScale;
- // โ Use passed zoom state if provided, otherwise fall back to global
- const activeZoomState = zoomState || {
- scale: furnitureViewSettings.scale,
- offsetX: furnitureViewSettings.offsetX,
- offsetY: furnitureViewSettings.offsetY,
- isZoomed: furnitureViewSettings.isZoomed
- };
- const { scale, offsetX, offsetY } = activeZoomState;
- console.log(`๐ drawFurnitureLayer DEBUG for: ${imagePath.split('/').pop()}`);
- console.log(` ๐ ZOOM STATE: scale=${scale}, offset=(${offsetX.toFixed(1)}, ${offsetY.toFixed(1)})`);
- console.log(` ๐ Using ${zoomState ? 'PASSED' : 'GLOBAL'} zoom state`);
- console.log(` Canvas size: ${width}x${height}`);
- try {
- const img = await loadImage(imagePath);
- if (!img) {
- console.error("โ Failed to load image:", imagePath);
- return;
- }
- console.log(` Original image: ${img.naturalWidth}x${img.naturalHeight}`);
- if (highRes) console.log(` ๐ High-res rendering: ${renderWidth}x${renderHeight}`);
- const scaledWidth = img.naturalWidth * scale; // Keep original logic
- const scaledHeight = img.naturalHeight * scale; // Keep original logic
- let drawX = (renderWidth / 2) - (scaledWidth / 2) + (offsetX * renderScale);
- let drawY = (renderHeight / 2) - (scaledHeight / 2) + (offsetY * renderScale);
- console.log(` Draw position: (${drawX.toFixed(1)}, ${drawY.toFixed(1)})`);
- // Create working canvas at render resolution
- const tempCanvas = document.createElement("canvas");
- tempCanvas.width = renderWidth;
- tempCanvas.height = renderHeight;
- const tempCtx = tempCanvas.getContext("2d");
- if (isMask && tintColor) {
- // โ CORRECTED WALL MASK LOGIC
- console.log(` ๐ญ Processing wall mask with color ${tintColor}`);
- // First, draw the mask image to get its alpha values
- const maskCanvas = document.createElement("canvas");
- maskCanvas.width = width;
- maskCanvas.height = height;
- const maskCtx = maskCanvas.getContext("2d");
- // Draw the scaled mask image
- maskCtx.drawImage(img, drawX, drawY, scaledWidth, scaledHeight);
- // Get the mask pixel data
- let maskImageData;
- try {
- maskImageData = maskCtx.getImageData(0, 0, width, height);
- } catch (e) {
- console.warn("โ ๏ธ Canvas tainted, falling back to simple draw for mask processing:", e.message);
- tempCtx.drawImage(img, drawX, drawY, scaledWidth, scaledHeight);
- ctx.drawImage(tempCanvas, 0, 0);
- return;
- }
- const maskData = maskImageData.data;
- // Create output canvas with the tint color
- const outputImageData = tempCtx.createImageData(width, height);
- const outputData = outputImageData.data;
- console.log("๐จ TINTING DEBUG:");
- console.log(" Image being tinted:", imagePath?.split('/').pop());
- console.log(" tintColor parameter:", tintColor);
- console.log(" Is sofa base:", imagePath?.includes('sofa-capitol-base'));
- // Parse tint color
- const hex = tintColor.replace("#", "");
- const r = parseInt(hex.substring(0, 2), 16);
- const g = parseInt(hex.substring(2, 4), 16);
- const b = parseInt(hex.substring(4, 6), 16);
- console.log(" Parsed RGB:", r, g, b);
- console.log(" Should be Cottage Linen RGB: 240, 240, 233");
- console.log(` ๐จ Tint color RGB: (${r}, ${g}, ${b})`);
- // Apply mask: white areas in mask = wall color, black areas = transparent
- for (let i = 0; i < maskData.length; i += 4) {
- const maskR = maskData[i];
- const maskG = maskData[i + 1];
- const maskB = maskData[i + 2];
- // Calculate mask intensity (how white the pixel is)
- const maskIntensity = (maskR + maskG + maskB) / 3;
- if (maskIntensity > 128) {
- // White area in mask = apply wall color
- outputData[i] = r;
- outputData[i + 1] = g;
- outputData[i + 2] = b;
- outputData[i + 3] = 128; // Fully opaque
- } else {
- // Black area in mask = transparent (let room background show through)
- outputData[i] = 0;
- outputData[i + 1] = 0;
- outputData[i + 2] = 0;
- outputData[i + 3] = 0; // Fully transparent
- }
- }
- // Put the processed image data to the temp canvas
- tempCtx.putImageData(outputImageData, 0, 0);
- console.log(` โ Wall mask applied: white areas colored, black areas transparent`);
- } else if (tintColor) {
- if (imagePath?.includes('sofa-capitol-base') || imagePath?.includes('patterns/botanicals/')) {
- console.log("๐จ Using LUMINANCE-based logic for:", imagePath?.split('/').pop());
- // Draw the image first
- tempCtx.drawImage(img, drawX, drawY, scaledWidth, scaledHeight);
- // Get image data
- let imageData;
- try {
- imageData = tempCtx.getImageData(0, 0, renderWidth, renderHeight);
- } catch (e) {
- console.warn("โ ๏ธ Canvas tainted, falling back to simple tinting for luminance processing:", e.message);
- // Fall back to simple tinting
- tempCtx.fillStyle = tintColor;
- tempCtx.fillRect(0, 0, renderWidth, renderHeight);
- tempCtx.globalCompositeOperation = "destination-in";
- tempCtx.drawImage(img, drawX, drawY, scaledWidth, scaledHeight);
- tempCtx.globalCompositeOperation = "source-over";
- ctx.drawImage(tempCanvas, 0, 0);
- return;
- }
- const data = imageData.data;
- // Parse tint color
- const hex = tintColor.replace("#", "");
- const rLayer = parseInt(hex.substring(0, 2), 16);
- const gLayer = parseInt(hex.substring(2, 4), 16);
- const bLayer = parseInt(hex.substring(4, 6), 16);
- // โ USE LUMINANCE for both sofa base AND patterns
- for (let i = 0; i < data.length; i += 4) {
- const r = data[i];
- const g = data[i + 1];
- const b = data[i + 2];
- const brightness = (r + g + b) / 3;
- if (brightness <= 5) { // Pure black - transparent
- data[i + 3] = 0;
- } else { // Non-black pixels - tint based on brightness
- const alpha = brightness / 255;
- data[i] = rLayer;
- data[i + 1] = gLayer;
- data[i + 2] = bLayer;
- data[i + 3] = Math.round(alpha * 255);
- }
- }
- tempCtx.putImageData(imageData, 0, 0);
- } else {
- // Keep original alpha-based logic for other elements (if any)
- tempCtx.fillStyle = tintColor;
- tempCtx.fillRect(0, 0, width, height);
- tempCtx.globalCompositeOperation = "destination-in";
- tempCtx.drawImage(img, drawX, drawY, scaledWidth, scaledHeight);
- tempCtx.globalCompositeOperation = "source-over";
- }
- }
- else {
- // Direct images - draw at calculated position and size
- tempCtx.drawImage(img, drawX, drawY, scaledWidth, scaledHeight);
- console.log(` โ Direct image drawn at (${drawX.toFixed(1)}, ${drawY.toFixed(1)})`);
- }
- // Draw to main canvas
- ctx.save();
- ctx.globalAlpha = opacity;
- console.log(" ๐จ Using NORMAL blend for", imagePath?.split('/').pop());
- ctx.globalCompositeOperation = blendMode; // Normal for everything else
- if (highRes) {
- // Scale down from high-res to normal resolution
- ctx.drawImage(tempCanvas, 0, 0, renderWidth, renderHeight, 0, 0, width, height);
- console.log(` โ High-res layer scaled down and composited`);
- } else {
- ctx.drawImage(tempCanvas, 0, 0);
- }
- ctx.restore();
- console.log(` โ Layer composited to main canvas`);
- } catch (error) {
- console.error("โ Error in drawFurnitureLayer:", error);
- }
- }
- // Create a color input UI element
- const createColorInput = (label, id, initialColor, isBackground = false) => {
- console.log(`Creating ${label} input, ID: ${id}, initialColor: ${initialColor}`);
- const container = document.createElement("div");
- container.className = "layer-input-container";
- const labelEl = document.createElement("div");
- labelEl.className = "layer-label";
- labelEl.textContent = label || "Unknown Layer";
- const colorCircle = document.createElement("div");
- colorCircle.className = "circle-input";
- colorCircle.id = `${id}Circle`;
- const cleanInitialColor = (initialColor || "Snowbound").replace(/^(SW|SC)\d+\s*/i, "").trim();
- const colorValue = lookupColor(cleanInitialColor);
- console.log(`Setting ${label} circle background to: ${colorValue}`);
- colorCircle.style.backgroundColor = colorValue;
- const input = document.createElement("input");
- input.type = "text";
- input.className = "layer-input";
- input.id = id;
- input.placeholder = `Enter ${label ? label.toLowerCase() : 'layer'} color`;
- input.value = toInitialCaps(cleanInitialColor);
- console.log(`Setting ${label} input value to: ${input.value}`);
- container.append(labelEl, colorCircle, input);
- const updateColor = () => {
- console.log(`updateColor called for ${label}, input value: ${input.value}`);
- const formatted = toInitialCaps(input.value.trim());
- if (!formatted) {
- input.value = toInitialCaps(cleanInitialColor);
- colorCircle.style.backgroundColor = colorValue;
- console.log(`${label} input restored to initial color: ${colorValue}`);
- } else {
- const hex = lookupColor(formatted) || "#FFFFFF";
- if (hex === "#FFFFFF" && formatted !== "Snowbound") {
- input.value = toInitialCaps(cleanInitialColor);
- colorCircle.style.backgroundColor = colorValue;
- console.log(`${label} input restored to initial color due to invalid color: ${colorValue}`);
- } else {
- input.value = formatted;
- colorCircle.style.backgroundColor = hex;
- console.log(`${label} input updated to: ${hex}`);
- }
- }
- const layerIndex = appState.currentLayers.findIndex(layer => layer.label === label);
- if (layerIndex !== -1) {
- appState.currentLayers[layerIndex].color = input.value;
- console.log("๐ฏ COLOR UPDATE DEBUG:");
- console.log(` Changed input: ${label} (index ${layerIndex})`);
- console.log(` New value: ${input.value}`);
- console.log(" Current layer structure after update:");
- appState.currentLayers.forEach((layer, i) => {
- console.log(` ${i}: ${layer.label} = "${layer.color}"`);
- });
- console.log(`Updated appState.currentLayers[${layerIndex}].color to: ${input.value}`);
- }
- const isFurniturePattern = appState.currentPattern?.isFurniture || false;
- // Check if we're in fabric mode - render both fabric mockup and pattern preview
- if (appState.isInFabricMode) {
- console.log("๐งต Color changed in fabric mode - calling both renderFabricMockup() and updatePreview()");
- renderFabricMockup();
- updatePreview(); // Also update the pattern preview on the left
- } else {
- // Regular furniture or pattern mode
- updatePreview();
- updateRoomMockup();
- }
- populateCoordinates();
- };
- // Restore original event listeners
- input.addEventListener("blur", updateColor);
- input.addEventListener("keydown", (e) => {
- if (e.key === "Enter") updateColor();
- });
- // Restore original click handler
- console.log(`Attaching click handler to ${label} color circle, ID: ${colorCircle.id}`);
- colorCircle.addEventListener("click", () => {
- // Check if we're in coordinate mode (back button exists) - exit coordinate mode
- const coordinateBackButton = document.getElementById('backToPatternLink');
- if (coordinateBackButton) {
- console.log(`๐ Color circle clicked in coordinate mode - triggering back to pattern then selecting layer`);
- coordinateBackButton.click(); // Trigger the coordinate back button
- // Pass through the click after returning to pattern mode
- setTimeout(() => {
- appState.lastSelectedLayer = {
- input,
- circle: colorCircle,
- label,
- isBackground
- };
- highlightActiveLayer(colorCircle);
- console.log(`โ Layer selected after returning from coordinate mode: ${label}`);
- }, 50);
- return;
- }
- // In furniture mode, allow normal color changes - do NOT exit furniture mode
- const furnitureBackButton = document.getElementById('backToPatternsBtn');
- if (furnitureBackButton) {
- console.log(`๐จ Color circle clicked in furniture mode - changing color while staying in furniture mode: ${label}`);
- // Continue with normal color selection behavior below
- }
- // Normal color circle behavior
- appState.lastSelectedLayer = {
- input,
- circle: colorCircle,
- label,
- isBackground
- };
- highlightActiveLayer(colorCircle);
- console.log(`Clicked ${label} color circle`);
- });
- return {
- container,
- input,
- circle: colorCircle,
- label,
- isBackground
- };
- };
- // Populate curated colors in header
- function populateCuratedColors(colors) {
- console.log("๐จ populateCuratedColors called with colors:", colors?.length);
- console.log("๐ curatedColorsContainer element:", dom.curatedColorsContainer);
- if (!dom.curatedColorsContainer) {
- console.error("โ curatedColorsContainer not found in DOM");
- console.log("๐ Available DOM elements:", Object.keys(dom));
- return;
- }
- if (!colors || !colors.length) {
- console.warn("โ ๏ธ No curated colors provided, colors array:", colors);
- return;
- }
- console.log("โ About to populate", colors.length, "curated colors");
- dom.curatedColorsContainer.innerHTML = "";
- // ๐๏ธ Run The Ticket Button
- const ticketCircle = document.createElement("div");
- ticketCircle.id = "runTheTicketCircle";
- ticketCircle.className = "curated-color-circle cursor-pointer border-2";
- ticketCircle.style.backgroundColor = "black";
- const ticketLabel = document.createElement("span");
- ticketLabel.className = "text-xs font-bold text-white text-center whitespace-pre-line font-special-elite";
- ticketLabel.textContent = appState.activeTicketNumber
- ? `TICKET\n${appState.activeTicketNumber}`
- : "RUN\nTHE\nTICKET";
- ticketCircle.appendChild(ticketLabel);
- ticketCircle.addEventListener("click", () => {
- const ticketNumber = prompt("๐๏ธ Enter the Sherwin-Williams Ticket Number:");
- if (ticketNumber) runStaticTicket(ticketNumber.trim());
- });
- dom.curatedColorsContainer.appendChild(ticketCircle);
- // ๐จ Add curated color swatches
- colors.forEach(label => {
- if (!Array.isArray(appState.colorsData)) {
- console.error("โ appState.colorsData is not available or not an array");
- return;
- }
- const found = appState.colorsData.find(c =>
- label.toLowerCase().includes(c.sw_number?.toLowerCase()) ||
- label.toLowerCase().includes(c.color_name?.toLowerCase())
- );
- if (!found || !found.hex) {
- console.warn("โ ๏ธ Missing hex for curated color:", label);
- return;
- }
- const hex = `#${found.hex}`;
- const circle = document.createElement("div");
- circle.className = "curated-color-circle cursor-pointer";
- circle.style.backgroundColor = hex;
- const text = document.createElement("span");
- text.className = `text-xs font-bold text-center ${getContrastClass(hex)} whitespace-pre-line`;
- text.textContent = `${found.sw_number?.toUpperCase()}\n${toInitialCaps(found.color_name)}`;
- circle.appendChild(text);
- circle.addEventListener("click", () => {
- const selectedLayer = appState.lastSelectedLayer;
- if (!selectedLayer) return alert("Please select a layer first.");
- selectedLayer.input.value = toInitialCaps(found.color_name);
- selectedLayer.circle.style.backgroundColor = hex;
- const i = appState.currentLayers.findIndex(l => l.label === selectedLayer.label);
- if (i !== -1) appState.currentLayers[i].color = found.color_name;
- const j = appState.layerInputs.findIndex(li => li.label === selectedLayer.label);
- if (j !== -1) {
- appState.layerInputs[j].input.value = toInitialCaps(found.color_name);
- appState.layerInputs[j].circle.style.backgroundColor = hex;
- }
- appState.lastSelectedColor = { name: found.color_name, hex };
- updateDisplays();
- });
- dom.curatedColorsContainer.appendChild(circle);
- });
- console.log("โ Curated colors populated:", colors.length);
- }
- function getLayerMappingForPreview(isFurnitureCollection) {
- if (isFurnitureCollection) {
- return {
- type: 'furniture',
- patternStartIndex: 2, // Pattern layers start at index 2
- backgroundIndex: 1, // Sofa base = pattern background (index 1)
- wallIndex: 0 // Wall color (index 0)
- };
- } else {
- return {
- type: 'standard',
- patternStartIndex: 1, // Pattern layers start at index 1
- backgroundIndex: 0, // True background
- wallIndex: null // No wall color
- };
- }
- }
- function validateLayerMapping() {
- const isFurnitureCollection = appState.selectedCollection?.furnitureType != null || appState.furnitureMode === true;
- const mapping = getLayerMappingForPreview(isFurnitureCollection);
- console.log("๐ LAYER MAPPING VALIDATION (WITH WALL COLOR):");
- console.log(" Collection type:", isFurnitureCollection ? "furniture" : "standard");
- console.log(" Total inputs:", appState.currentLayers.length);
- console.log(" Pattern start index:", mapping.patternStartIndex);
- console.log(" Background/Sofa base index:", mapping.backgroundIndex);
- console.log(" Wall index:", mapping.wallIndex);
- console.log(" Layer assignments:");
- appState.currentLayers.forEach((layer, index) => {
- let usage = "unused";
- if (index === mapping.wallIndex) {
- usage = "wall color (via mask)";
- } else if (index === mapping.backgroundIndex) {
- if (isFurnitureCollection) {
- usage = "sofa base + pattern background";
- } else {
- usage = "pattern background";
- }
- } else if (index >= mapping.patternStartIndex) {
- usage = `pattern layer ${index - mapping.patternStartIndex}`;
- }
- console.log(` ${index}: ${layer.label} = "${layer.color}" (${usage})`);
- });
- // Show the mapping clearly
- if (isFurnitureCollection) {
- console.log("๐ FURNITURE COLLECTION MAPPING (WITH WALL MASK):");
- console.log(" Pattern Preview:");
- console.log(` Background โ Input ${mapping.backgroundIndex} (${appState.currentLayers[mapping.backgroundIndex]?.label})`);
- for (let i = 0; i < (appState.currentLayers.length - mapping.patternStartIndex); i++) {
- const inputIndex = mapping.patternStartIndex + i;
- if (appState.currentLayers[inputIndex]) {
- console.log(` Pattern Layer ${i} โ Input ${inputIndex} (${appState.currentLayers[inputIndex].label})`);
- }
- }
- console.log(" Furniture Mockup:");
- console.log(" Room Scene โ sofa-capitol.png");
- console.log(` Wall Areas โ Input ${mapping.wallIndex} (${appState.currentLayers[mapping.wallIndex]?.label}) via wall mask`);
- console.log(` Sofa Base โ Input ${mapping.backgroundIndex} (${appState.currentLayers[mapping.backgroundIndex]?.label})`);
- for (let i = 0; i < (appState.currentLayers.length - mapping.patternStartIndex); i++) {
- const inputIndex = mapping.patternStartIndex + i;
- if (appState.currentLayers[inputIndex]) {
- console.log(` Pattern Layer ${i} โ Input ${inputIndex} (${appState.currentLayers[inputIndex].label})`);
- }
- }
- }
- }
- function insertTicketIndicator(ticketNumber) {
- const existing = document.getElementById("ticketIndicator");
- if (existing) {
- existing.innerHTML = `TICKET<br>${ticketNumber}`;
- return;
- }
- const indicator = document.createElement("div");
- indicator.id = "ticketIndicator";
- indicator.className = "w-20 h-20 rounded-full flex items-center justify-center text-center text-xs font-bold text-gray-800";
- indicator.style.backgroundColor = "#e5e7eb"; // Tailwind gray-200
- indicator.style.marginRight = "8px";
- indicator.innerHTML = `TICKET<br>${ticketNumber}`;
- dom.curatedColorsContainer.prepend(indicator);
- }
- function promptTicketNumber() {
- const input = prompt("Enter Sherwin-Williams ticket number (e.g., 280):");
- const ticketNum = parseInt(input?.trim());
- if (isNaN(ticketNum)) {
- alert("Please enter a valid numeric ticket number.");
- return;
- }
- runStaticTicket(ticketNum);
- }
- function runTheTicket(baseColor) {
- console.log("รฐลธลฝลธรฏยธย Running the Ticket for:", baseColor);
- if (!isAppReady) {
- console.warn("โ ๏ธ App is not ready yet. Ignoring runTheTicket call.");
- alert("Please wait while the app finishes loading.");
- return;
- }
- if (!baseColor || !baseColor.hex) {
- console.warn("รขยล No base color provided to runTheTicket.");
- return;
- }
- if (!Array.isArray(appState.colorsData) || appState.colorsData.length === 0) {
- console.warn("Xยธย Sherwin-Williams colors not loaded yet.");
- alert("Color data is still loading. Please try again shortly.");
- return;
- }
- const baseHSL = hexToHSL(baseColor.hex);
- if (!baseHSL) {
- console.error("X Failed to convert base HEX to HSL.");
- return;
- }
- console.log("+ Base color HSL:", baseHSL);
- const swColors = appState.colorsData
- .filter(c => c.hex && c.name)
- .map(c => ({
- name: c.name,
- hex: c.hex,
- hsl: hexToHSL(c.hex)
- }));
- console.log("** Total SW Colors to search:", swColors.length);
- const scored = swColors
- .map(c => {
- const hueDiff = Math.abs(baseHSL.h - c.hsl.h);
- const satDiff = Math.abs(baseHSL.s - c.hsl.s);
- const lightDiff = Math.abs(baseHSL.l - c.hsl.l);
- return {
- ...c,
- score: hueDiff + satDiff * 0.5 + lightDiff * 0.8
- };
- })
- .sort((a, b) => a.score - b.score)
- .slice(0, appState.currentLayers.length);
- console.log("รฐลธลฝยฏ Top Ticket matches:", scored);
- if (!Array.isArray(appState.layerInputs) || appState.layerInputs.length === 0) {
- console.warn("รขยล No layer inputs available. Cannot apply ticket.");
- return;
- }
- scored.forEach((ticketColor, idx) => {
- const inputSet = appState.layerInputs[idx];
- if (!inputSet || !inputSet.input || !inputSet.circle) {
- console.warn(`รขยล Missing input or circle at index ${idx}`);
- return;
- }
- const formatted = toInitialCaps(ticketColor.name);
- inputSet.input.value = formatted;
- inputSet.circle.style.backgroundColor = ticketColor.hex;
- appState.currentLayers[idx].color = formatted;
- console.log(`รฐลธลฝยฏ Layer ${idx + 1} set to ${formatted} (${ticketColor.hex})`);
- });
- insertTicketIndicator(ticketNumber);
- updateDisplays();
- console.log("โ Ticket run complete.");
- }
- function runStaticTicket(ticketNumber) {
- console.log(`รฐลธลฝยซ Static Ticket Requested: ${ticketNumber}`);
- if (!Array.isArray(appState.colorsData) || appState.colorsData.length === 0) {
- alert("Color data not loaded yet.");
- return;
- }
- const ticketColors = [];
- for (let i = 1; i <= 7; i++) {
- const locatorId = `${ticketNumber}-C${i}`;
- const color = appState.colorsData.find(c => c.locator_id?.toUpperCase() === locatorId.toUpperCase());
- if (color) {
- const displayName = `${color.sw_number?.toUpperCase() || ""} ${toInitialCaps(color.color_name)}`;
- ticketColors.push(displayName.trim());
- }
- }
- if (ticketColors.length === 0) {
- alert(`No colors found for ticket ${ticketNumber}`);
- return;
- }
- appState.curatedColors = ticketColors;
- appState.activeTicketNumber = ticketNumber; // รฐลธโ โข Track it for label update
- populateCuratedColors(ticketColors);
- console.log(`รฐลธลฝยฏ Loaded ticket ${ticketNumber} with ${ticketColors.length} colors`);
- }
- async function initializeApp() {
- console.log("๐ Starting app...");
- // Validate DOM elements first
- validateDOMElements();
- // โ Step 1: Load Sherwin-Williams Colors
- await loadColors();
- console.log("โ Colors loaded:", appState.colorsData.length);
- try {
- // โ Step 2: Load Collections
- // Check if data is embedded in window object (Shopify mode)
- let data;
- if (window.ColorFlexData && window.ColorFlexData.collections) {
- console.log("๐ฏ Using embedded ColorFlex data");
- data = { collections: window.ColorFlexData.collections };
- } else {
- console.log("๐ Loading collections from Shopify assets");
- const collectionsUrl = window.ColorFlexAssets?.collectionsUrl || "/assets/collections.json";
- const response = await fetch(collectionsUrl, {
- method: 'GET',
- cache: "no-store",
- headers: {
- 'Content-Type': 'application/json',
- }
- });
- if (!response.ok) throw new Error(`Failed to fetch collections: ${response.status}`);
- data = await response.json();
- }
- // ADD THIS DEBUG:
- console.log("๐ Raw JSON collections loaded:", data.collections.length);
- const farmhouseCollection = data.collections.find(c => c.name === "farmhouse");
- console.log("๐ Raw farmhouse collection:", farmhouseCollection);
- console.log("๐ Raw farmhouse elements:", farmhouseCollection?.elements);
- if (!data.collections?.length) {
- console.error("X No collections found in collections.json");
- dom.collectionHeader.textContent = "No Collections Available";
- dom.preview.innerHTML = "<p>No collections available. Please run the data import script.</p>";
- return;
- }
- // โ Step 3: Save collections once
- if (!appState.collections.length) {
- appState.collections = data.collections;
- console.log("โ Collections loaded:", appState.collections.length);
- }
- // โ Step 4: Select collection via Shopify integration, URL param, or fallback
- const urlParams = new URLSearchParams(window.location.search);
- const urlCollectionName = urlParams.get("collection")?.trim();
- console.log("๐ COLLECTION SELECTION DEBUG:");
- console.log(" URL collection param:", urlCollectionName);
- console.log(" Shopify target collection:", window.appState?.selectedCollection);
- console.log(" Shopify target pattern:", window.appState?.targetPattern?.name);
- console.log(" Available collections:", appState.collections.map(c => c.name));
- console.log(" Total collections loaded:", appState.collections.length);
- // Priority 1: Use Shopify-detected collection (from product page integration)
- let collectionName = window.appState?.selectedCollection || urlCollectionName;
- let selectedCollection = appState.collections.find(
- c => c.name.trim().toLowerCase() === collectionName?.toLowerCase()
- ) || appState.collections[0];
- console.log(" Selected collection source:", window.appState?.selectedCollection ? "Shopify" : "URL");
- console.log(" Final collection:", selectedCollection?.name);
- if (!selectedCollection) {
- console.error("X No valid collection found.");
- return;
- }
- // โ Step 5: Set collection in appState
- appState.selectedCollection = selectedCollection;
- appState.lockedCollection = true;
- appState.curatedColors = selectedCollection.curatedColors || [];
- console.log("@ Selected Collection:", selectedCollection.name);
- console.log("@ Curated colors:", appState.curatedColors.length);
- // โ Step 6: Update UI header
- if (dom.collectionHeader) {
- dom.collectionHeader.textContent = toInitialCaps(selectedCollection.name);
- }
- // โ Step 7: Show curated color circles + ticket button
- populateCuratedColors(appState.curatedColors);
- // โ Step 8: Load target pattern or first pattern
- // Priority 1: Check URL pattern parameter
- let initialPattern = null;
- const urlPatternName = urlParams.get("pattern")?.trim();
- if (urlPatternName) {
- // First try to find pattern in selected collection
- initialPattern = selectedCollection.patterns.find(p =>
- p.name?.toLowerCase() === urlPatternName.toLowerCase() ||
- p.id === urlPatternName
- ) || selectedCollection.patterns.find(p =>
- p.name?.toLowerCase().includes(urlPatternName.toLowerCase()) ||
- urlPatternName.toLowerCase().includes(p.name?.toLowerCase())
- );
- // If pattern not found in selected collection, search all collections (DYNAMIC)
- if (!initialPattern) {
- console.log("๐ Pattern not found in selected collection, searching all collections dynamically...");
- console.log(`๐ Searching for pattern: "${urlPatternName}" across ${appState.collections.length} collections`);
- for (const collection of appState.collections) {
- console.log(` ๐ Checking collection: "${collection.name}" (${collection.patterns?.length || 0} patterns)`);
- const foundPattern = collection.patterns?.find(p => {
- const patternName = p.name?.toLowerCase() || '';
- const patternId = p.id?.toLowerCase() || '';
- const searchName = urlPatternName.toLowerCase();
- // Exact matches first
- if (patternName === searchName || patternId === searchName) return true;
- // Partial matches
- if (patternName.includes(searchName) || searchName.includes(patternName)) return true;
- // Handle special cases for known patterns
- if (searchName === 'constantinople' && patternName.includes('constantinople')) return true;
- if (searchName === 'istanbul' && patternName.includes('istanbul')) return true;
- return false;
- });
- if (foundPattern) {
- console.log(`๐ฏ FOUND: Pattern "${urlPatternName}" โ "${foundPattern.name}" in collection "${collection.name}"`);
- console.log(`๐ Switching from collection "${selectedCollection.name}" to "${collection.name}"`);
- selectedCollection = collection;
- appState.selectedCollection = selectedCollection;
- appState.curatedColors = selectedCollection.curatedColors || [];
- initialPattern = foundPattern;
- // Update UI to reflect correct collection
- if (dom.collectionHeader) {
- dom.collectionHeader.textContent = toInitialCaps(selectedCollection.name);
- }
- populateCuratedColors(appState.curatedColors);
- break;
- }
- }
- if (!initialPattern) {
- console.warn(`โ Pattern "${urlPatternName}" not found in any collection`);
- }
- }
- console.log("๐ฏ Using URL pattern parameter:", urlPatternName, "โ", initialPattern?.name, "in collection:", selectedCollection?.name);
- }
- // Priority 2: Use Shopify-detected target pattern
- if (!initialPattern && window.appState?.targetPattern) {
- initialPattern = selectedCollection.patterns.find(p =>
- p.id === window.appState.targetPattern.id ||
- p.name === window.appState.targetPattern.name
- );
- console.log("๐ฏ Using Shopify target pattern:", initialPattern?.name);
- }
- // Priority 3: Use first pattern as fallback
- if (!initialPattern) {
- initialPattern = selectedCollection.patterns[0];
- console.log("๐ Using first pattern as fallback:", initialPattern?.name);
- }
- const initialPatternId = initialPattern?.id;
- if (initialPatternId) {
- loadPatternData(selectedCollection, initialPatternId); // โ Fixed: pass collection
- } else {
- console.warn("รขลก รฏยธย No patterns found for", selectedCollection.name);
- }
- // โ Step 9: Load thumbnails + setup print
- populatePatternThumbnails(selectedCollection.patterns);
- setupPrintListener();
- isAppReady = true;
- console.log("โ App is now fully ready.");
- function initializeInteractiveZoom() {
- // Set up interactive zoom when app is ready
- if (document.readyState === 'loading') {
- document.addEventListener('DOMContentLoaded', addInteractiveZoom);
- } else {
- addInteractiveZoom();
- }
- }
- // Call this when collections are loaded
- initializeInteractiveZoom(); // โ Add this line right here
- initializeTryFurnitureFeature();
- console.log("Current state during app init:");
- console.log(" furnitureConfig loaded:", !!furnitureConfig);
- console.log(" appState.selectedCollection:", !!appState.selectedCollection);
- console.log(" appState.collections:", !!appState.collections?.length);
- console.log(" DOM ready:", document.readyState);
- } catch (error) {
- console.error("X Error loading collections:", error);
- dom.collectionHeader.textContent = "Error Loading Collection";
- dom.preview.innerHTML = "<p>Error loading data. Please try refreshing.</p>";
- }
- }
- // Ensure appState has a default
- appState._selectedCollection = null;
- // Run on initial load and refresh
- window.addEventListener('load', () => {
- initializeApp().catch(error => console.error("Initialization failed:", error));
- });
- window.addEventListener('popstate', () => {
- initializeApp().catch(error => console.error("Refresh initialization failed:", error));
- });
- // Populate pattern thumbnails in sidebar
- function populatePatternThumbnails(patterns) {
- console.log("populatePatternThumbnails called with patterns:", patterns);
- if (!dom.collectionThumbnails) {
- console.error("collectionThumbnails not found in DOM");
- return;
- }
- if (!Array.isArray(patterns)) {
- console.error("Patterns is not an array:", patterns);
- return;
- }
- const validPatterns = patterns.filter(p => p && typeof p === 'object' && p.name);
- if (!validPatterns.length) {
- console.warn("No valid patterns to display");
- dom.collectionThumbnails.innerHTML = "<p>No patterns available.</p>";
- return;
- }
- function cleanPatternName(str) {
- return str
- .toLowerCase()
- .replace(/\.\w+$/, '')
- .replace(/-\d+x\d+$|-variant$/i, '')
- .replace(/^\d+[a-z]+-|-.*$/i, '')
- .replace(/\s+/g, ' ')
- .trim()
- .split(' ')
- .map(word => word.charAt(0).toUpperCase() + word.slice(1))
- .join(" ");
- }
- dom.collectionThumbnails.innerHTML = "";
- console.log("Cleared existing thumbnails");
- validPatterns.forEach(pattern => {
- console.log("Processing pattern:", pattern);
- pattern.displayName = cleanPatternName(pattern.name);
- const thumb = document.createElement("div");
- thumb.className = "thumbnail cursor-pointer border-1 border-transparent";
- thumb.dataset.patternId = pattern.id || pattern.name.toLowerCase().replace(/\s+/g, '-');
- thumb.style.width = "120px";
- thumb.style.boxSizing = "border-box";
- const img = document.createElement("img");
- img.src = normalizePath(pattern.thumbnail) || "https://so-animation.com/colorflex/data/collections/fallback.jpg";
- img.alt = pattern.displayName;
- img.className = "w-full h-auto";
- img.onerror = () => {
- console.warn(`Failed to load thumbnail for ${pattern.displayName}: ${img.src}`);
- if (img.src !== "https://so-animation.com/colorflex/data/collections/fallback.jpg") {
- img.src = "https://so-animation.com/colorflex/data/collections/fallback.jpg";
- img.onerror = () => {
- console.warn(`Failed to load fallback for ${pattern.displayName}`);
- const placeholder = document.createElement("div");
- placeholder.textContent = pattern.displayName || "Thumbnail Unavailable";
- placeholder.style.width = "100%";
- placeholder.style.height = "80px";
- placeholder.style.backgroundColor = "#e0e0e0";
- placeholder.style.border = "1px solid #ccc";
- placeholder.style.display = "flex";
- placeholder.style.alignItems = "center";
- placeholder.style.justifyContent = "center";
- placeholder.style.fontSize = "12px";
- placeholder.style.textAlign = "center";
- placeholder.style.padding = "5px";
- placeholder.style.boxSizing = "border-box";
- thumb.replaceChild(placeholder, img);
- img.onerror = null;
- console.log(`Replaced failed thumbnail for ${pattern.displayName} with placeholder div`);
- };
- } else {
- const placeholder = document.createElement("div");
- placeholder.textContent = pattern.displayName || "Thumbnail Unavailable";
- placeholder.style.width = "100%";
- placeholder.style.height = "80px";
- placeholder.style.backgroundColor = "#e0e0e0";
- placeholder.style.border = "1px solid #ccc";
- placeholder.style.display = "flex";
- placeholder.style.alignItems = "center";
- placeholder.style.justifyContent = "center";
- placeholder.style.fontSize = "12px";
- placeholder.style.textAlign = "center";
- placeholder.style.padding = "5px";
- placeholder.style.boxSizing = "border-box";
- thumb.replaceChild(placeholder, img);
- img.onerror = null;
- console.log(`Replaced failed thumbnail for ${pattern.displayName} with placeholder div`);
- }
- };
- thumb.appendChild(img);
- const label = document.createElement("p");
- label.textContent = pattern.displayName;
- label.className = "text-center";
- thumb.appendChild(label);
- if (appState.currentPattern && String(appState.currentPattern.id) === String(pattern.id)) {
- thumb.classList.add("selected");
- console.log(`Applied 'selected' class to ${pattern.displayName}`);
- }
- thumb.addEventListener("click", (e) => {
- console.log(`Thumbnail clicked: ${pattern.displayName}, ID: ${thumb.dataset.patternId}`);
- handleThumbnailClick(thumb.dataset.patternId);
- document.querySelectorAll(".thumbnail").forEach(t => t.classList.remove("selected"));
- thumb.classList.add("selected");
- });
- dom.collectionThumbnails.appendChild(thumb);
- });
- console.log("Pattern thumbnails populated:", validPatterns.length);
- // Update collection header
- if (dom.collectionHeader) {
- dom.collectionHeader.textContent = toInitialCaps(appState.selectedCollection?.name || "Unknown");
- console.log("Updated collectionHeader:", dom.collectionHeader.textContent);
- }
- }
- // Populate coordinates thumbnails in #coordinatesContainer
- const populateCoordinates = () => {
- if (!dom.coordinatesContainer) {
- console.error("coordinatesContainer not found in DOM");
- return;
- }
- dom.coordinatesContainer.innerHTML = "";
- const coordinates = appState.selectedCollection?.coordinates || [];
- console.log("Collection coordinates data:", coordinates);
- if (!coordinates.length) {
- console.log("No matching coordinates available for collection:", appState.selectedCollection?.name);
- return;
- }
- const numCoordinates = coordinates.length;
- const xStep = 80;
- const yStep = 60;
- // Get actual container dimensions
- const containerWidth = dom.coordinatesContainer.offsetWidth || 600;
- const containerHeight = dom.coordinatesContainer.offsetHeight || 300;
- // Calculate total span and center the layout
- const totalXSpan = (numCoordinates - 1) * xStep;
- const totalYSpan = numCoordinates > 1 ? yStep : 0;
- const xStart = (containerWidth / 2) - (totalXSpan / 2);
- const yStart = (containerHeight / 2) - (totalYSpan / 2);
- coordinates.forEach((coord, index) => {
- const div = document.createElement("div");
- div.className = "coordinate-item";
- const xOffset = xStart + (index * xStep);
- const yOffset = yStart + (index % 2 === 0 ? 0 : yStep);
- div.style.setProperty("--x-offset", `${xOffset}px`);
- div.style.setProperty("--y-offset", `${yOffset}px`);
- const img = document.createElement("img");
- const normalizedPath = normalizePath(coord.path);
- console.log(`๐ Coordinate path: "${coord.path}" โ normalized: "${normalizedPath}"`);
- img.src = normalizedPath || "https://so-animation.com/colorflex/data/collections/default-coordinate.jpg";
- img.alt = coord.pattern || `Coordinate ${index + 1}`;
- img.className = "coordinate-image";
- img.dataset.filename = coord.path || "fallback";
- img.onerror = () => {
- console.warn(`Failed to load coordinate image: ${img.src}`);
- const placeholder = document.createElement("div");
- placeholder.className = "coordinate-placeholder";
- placeholder.textContent = coord.pattern || "Coordinate Unavailable";
- div.replaceChild(placeholder, img);
- };
- div.appendChild(img);
- dom.coordinatesContainer.appendChild(div);
- });
- console.log("Coordinates populated:", coordinates.length);
- setupCoordinateImageHandlers();
- };
- // Populate the layer inputs UI
- function populateLayerInputs(pattern = appState.currentPattern) {
- try {
- console.log("๐๏ธ populateLayerInputs called with pattern:", pattern?.name);
- if (!pattern) {
- console.error("โ No pattern provided or set in appState.");
- return;
- }
- handlePatternSelection(pattern.name);
- appState.layerInputs = [];
- appState.currentLayers = [];
- if (!dom.layerInputsContainer) {
- console.error("โ layerInputsContainer not found in DOM");
- console.log("๐ Available DOM elements:", Object.keys(dom));
- return;
- }
- console.log("โ layerInputsContainer found:", dom.layerInputsContainer);
- const designerColors = pattern.designer_colors || [];
- // Get all layers (including shadows)
- const allLayers = buildLayerModel(
- pattern,
- designerColors,
- {
- isWallPanel: appState.selectedCollection?.name === "wall-panels",
- tintWhite: appState.tintWhite || false
- }
- );
- // Store all layers in currentLayers
- appState.currentLayers = allLayers;
- dom.layerInputsContainer.innerHTML = "";
- // Create inputs ONLY for non-shadow layers
- const inputLayers = allLayers.filter(layer => !layer.isShadow);
- // Add inputs directly to container (no row wrappers)
- inputLayers.forEach(layer => {
- const layerData = createColorInput(
- layer.label,
- layer.inputId,
- layer.color,
- layer.isBackground
- );
- appState.layerInputs.push({
- input: layerData.input,
- circle: layerData.circle,
- label: layerData.label,
- isBackground: layerData.isBackground,
- color: layer.color,
- hex: lookupColor(layer.color) || "#FFFFFF"
- });
- // Add directly to container - no row grouping needed!
- dom.layerInputsContainer.appendChild(layerData.container);
- });
- console.log("โ Populated layerInputs:", appState.layerInputs.map(l => ({
- label: l.label,
- value: l.input.value
- })));
- console.log("โ All layers (including shadows):", appState.currentLayers.map(l => ({
- label: l.label,
- isShadow: l.isShadow,
- path: l.path
- })));
- // Add save button after pattern layers are populated
- addSaveButton();
- } catch (e) {
- console.error("โ Error in populateLayerInputs:", e);
- }
- }
- if (USE_GUARD && DEBUG_TRACE) {
- populateLayerInputs = guard(traceWrapper(populateLayerInputs, "populateLayerInputs"));
- } else if (USE_GUARD) {
- populateLayerInputs = guard(populateLayerInputs, "populateLayerInputs");
- }
- if (USE_GUARD && DEBUG_TRACE) {
- populateLayerInputs = guard(traceWrapper(populateLayerInputs, "populateLayerInputs"));
- } else if (USE_GUARD) {
- populateLayerInputs = guard(populateLayerInputs, "populateLayerInputs");
- }
- function handlePatternSelection(patternName, preserveColors = false) {
- console.log(`handlePatternSelection: pattern=${patternName}, lockedCollection=${appState.lockedCollection}, currentCollection=${appState.selectedCollection?.name}`);
- const pattern = appState.selectedCollection.patterns.find(
- p => p.name.toUpperCase() === patternName.toUpperCase()
- ) || appState.selectedCollection.patterns[0];
- if (!pattern) {
- console.error(`Pattern ${patternName} not found in selected collection`);
- return;
- }
- appState.currentPattern = pattern;
- console.log("Pattern set to:", appState.currentPattern.name);
- console.log("Layer labels available:", appState.currentPattern.layerLabels);
- console.log("Layers available:", JSON.stringify(appState.currentPattern.layers, null, 2));
- const designerColors = appState.currentPattern.designer_colors || [];
- const curatedColors = appState.selectedCollection.curatedColors || [];
- const colorSource = designerColors.length > 0 ? designerColors : curatedColors;
- console.log("Color source:", JSON.stringify(colorSource, null, 2));
- // Save current color values if preserving
- const savedColors = preserveColors ?
- appState.currentLayers.map(layer => layer.color) : [];
- appState.currentLayers = [];
- let colorIndex = 0; // โ Make sure this is only declared once
- const patternType = getPatternType(pattern, appState.selectedCollection);
- console.log(`๐ Pattern type detected: ${patternType} for pattern: ${pattern.name} in collection: ${appState.selectedCollection?.name}`);
- const isWallPanel = patternType === "wall-panel";
- const isWall = pattern.isWall || isWallPanel;
- if (isWall) {
- const wallColor = colorSource[colorIndex] || "#FFFFFF";
- appState.currentLayers.push({
- imageUrl: null,
- color: wallColor,
- label: "Wall Color",
- isShadow: false
- });
- colorIndex++;
- }
- const backgroundColor = colorSource[colorIndex] || "#FFFFFF";
- appState.currentLayers.push({
- imageUrl: null,
- color: backgroundColor,
- label: "Background",
- isShadow: false
- });
- colorIndex++;
- if (!appState.currentPattern.tintWhite) {
- const overlayLayers = pattern.layers || [];
- console.log(`Processing ${overlayLayers.length} overlay layers`);
- overlayLayers.forEach((layer, index) => {
- const layerPath = layer.path || "";
- const label = pattern.layerLabels[index] || `Layer ${index + 1}`;
- const isShadow = layer.isShadow === true;
- if (!isShadow) {
- const layerColor = colorSource[colorIndex] || "#000000";
- appState.currentLayers.push({
- imageUrl: layerPath,
- color: layerColor,
- label: label,
- isShadow: false
- });
- console.log(`Assigned color to ${label}: ${layerColor}`);
- colorIndex++;
- }
- });
- console.log("Final appState.currentLayers:", JSON.stringify(appState.currentLayers, null, 2));
- }
- // Restore saved colors if preserving
- if (preserveColors && savedColors.length > 0) {
- appState.currentLayers.forEach((layer, index) => {
- if (savedColors[index] && layer.color) {
- layer.color = savedColors[index];
- }
- });
- console.log("๐ Colors preserved from previous selection");
- }
- }
- function applyColorsToLayerInputs(colors, curatedColors = []) {
- console.log("Applying colors to layer inputs:", colors,
- "Curated colors:", curatedColors,
- "Layer inputs length:", appState.layerInputs.length,
- "Current layers length:", appState.currentLayers.length);
- appState.layerInputs.forEach((layer, index) => {
- if (index >= appState.currentLayers.length) {
- console.warn(`Skipping input ${layer.label} at index ${index}: no corresponding currentLayer`);
- return;
- }
- const color = colors[index] || curatedColors[index] || (layer.isBackground ? "#FFFFFF" : "Snowbound");
- const cleanColor = color.replace(/^(SW|SC)\d+\s*/i, "").trim();
- const hex = lookupColor(color) || "#FFFFFF";
- layer.input.value = toInitialCaps(cleanColor);
- layer.circle.style.backgroundColor = hex;
- console.log(`Applied ${cleanColor} (${hex}) to ${layer.label} input (index ${index})`);
- appState.currentLayers[index].color = cleanColor;
- });
- console.log("Inputs after apply:",
- appState.layerInputs.map(l => ({ id: l.input.id, label: l.label, value: l.input.value })));
- updateDisplays();
- }
- // Highlight active layer
- const highlightActiveLayer = (circle) => {
- document.querySelectorAll(".circle-input").forEach((c) => (c.style.outline = "none"));
- circle.style.outline = "6px solid rgb(244, 255, 219)";
- };
- // Fixed processImage function with corrected normalization logic
- let processImage = (url, callback, layerColor = '#7f817e', gamma = 2.2, isShadow = false, isWallPanel = false, isWall = false) => {
- console.log("๐ processImage called from:", new Error().stack.split('\n')[2]);
- // Normalize the URL path to fix ./data/ vs data/ inconsistencies
- const normalizedUrl = normalizePath(url);
- console.log(`Processing image ${url} -> ${normalizedUrl} with color ${layerColor}, Normalization: ${USE_NORMALIZATION}, IsShadow: ${isShadow}, IsWallPanel: ${isWallPanel}, IsWall: ${isWall}`);
- const img = new Image();
- img.crossOrigin = "Anonymous";
- img.src = `${normalizedUrl}?t=${new Date().getTime()}`;
- img.onload = () => {
- console.log(`โ Processed image: ${img.src} (${img.naturalWidth}x${img.naturalHeight})`);
- console.log("Image loaded successfully:", url);
- const canvas = document.createElement("canvas");
- const ctx = canvas.getContext("2d");
- const width = img.width;
- const height = img.height;
- canvas.width = width;
- canvas.height = height;
- if (isWall && (!url || url === "")) {
- ctx.fillStyle = layerColor;
- ctx.fillRect(0, 0, width, height);
- console.log("Applied solid wall color:", layerColor);
- callback(canvas);
- return;
- }
- ctx.drawImage(img, 0, 0, width, height);
- let imageData;
- try {
- imageData = ctx.getImageData(0, 0, width, height);
- } catch (e) {
- console.warn("โ ๏ธ Canvas tainted, returning image without processing:", e.message);
- callback(canvas);
- return;
- }
- const data = imageData.data;
- console.log("Original Sample (R,G,B,A):", data[0], data[1], data[2], data[3]);
- let rLayer, gLayer, bLayer;
- if (layerColor && !isShadow) {
- const hex = layerColor.replace("#", "");
- rLayer = parseInt(hex.substring(0, 2), 16);
- gLayer = parseInt(hex.substring(2, 4), 16);
- bLayer = parseInt(hex.substring(4, 6), 16);
- console.log(`Layer color parsed: R=${rLayer}, G=${gLayer}, B=${bLayer}`);
- } else if (isShadow) {
- console.log("Shadow layer: Skipping color parsing");
- }
- if (isWallPanel && layerColor && !isShadow) {
- // Wall panel processing
- const isDesignLayer = url.toLowerCase().includes("design");
- const isBackLayer = url.toLowerCase().includes("back");
- const layerType = isDesignLayer ? "Design" : isBackLayer ? "Back" : "Other";
- let designPixelCount = 0;
- let transparentPixelCount = 0;
- console.log(`๐ Wall panel debug - Layer type: ${layerType}`);
- console.log(`๐ Data array length: ${data.length}`);
- console.log(`๐ Image dimensions: ${canvas.width}x${canvas.height}`);
- console.log(`๐ Expected pixels: ${canvas.width * canvas.height}`);
- console.log(`๐ First 3 pixels:`,
- `(${data[0]},${data[1]},${data[2]},${data[3]})`,
- `(${data[4]},${data[5]},${data[6]},${data[7]})`,
- `(${data[8]},${data[9]},${data[10]},${data[11]})`);
- applyNormalizationProcessing(data, rLayer, gLayer, bLayer);
- console.log(`Processed ${layerType} layer: Design pixels=${designPixelCount}, Transparent pixels=${transparentPixelCount}`);
- } else if (isShadow) {
- // Shadow processing
- console.log("๐ Processing shadow layer");
- for (let i = 0; i < data.length; i += 4) {
- const luminance = 0.299 * data[i] + 0.587 * data[i + 1] + 0.114 * data[i + 2];
- const alpha = 1 - (luminance / 255);
- data[i] = 0;
- data[i + 1] = 0;
- data[i + 2] = 0;
- data[i + 3] = Math.round(alpha * 255);
- }
- } else if (layerColor && USE_NORMALIZATION) {
- // Standard pattern normalization
- applyNormalizationProcessing(data, rLayer, gLayer, bLayer);
- } else if (layerColor) {
- // Standard brightness-based masking (when normalization is off)
- let recoloredPixels = 0;
- let maskedPixels = 0;
- for (let i = 0; i < data.length; i += 4) {
- const r = data[i];
- const g = data[i + 1];
- const b = data[i + 2];
- const a = data[i + 3];
- const brightness = (r + g + b) / 3;
- if (brightness < 200 && a > 0) {
- data[i] = rLayer;
- data[i + 1] = gLayer;
- data[i + 2] = bLayer;
- data[i + 3] = 255;
- recoloredPixels++;
- } else {
- data[i + 3] = 0;
- maskedPixels++;
- }
- }
- console.log(`Recolored pixels: ${recoloredPixels}, Transparent (masked): ${maskedPixels}`);
- }
- console.log("Processed Sample (R,G,B,A):", data[0], data[1], data[2], data[3]);
- ctx.putImageData(imageData, 0, 0);
- callback(canvas);
- };
- img.onerror = () => console.error(`Canvas image load failed: ${url}`);
- };
- // GUARD / TRACE WRAPPER
- if (USE_GUARD && DEBUG_TRACE) {
- processImage = guard(traceWrapper(processImage, "processImage")); // Wrapped for debugging
- } else if (USE_GUARD) {
- processImage = guard(processImage, "processImage"); // Wrapped for debugging
- }
- // Load pattern data from JSON
- function loadPatternData(collection, patternId) {
- console.log(`loadPatternData: patternId=${patternId}`);
- const pattern = collection.patterns.find(p => p.id === patternId);
- if (pattern) {
- console.log(`โ Found pattern "${pattern.name}" (ID: ${pattern.id}) in collection "${collection.name}"`);
- appState.currentPattern = pattern;
- // ===== INSERT DEBUG LOGS HERE =====
- console.log("๐ SOURCE DATA DEBUG:");
- console.log(" Current pattern:", appState.currentPattern?.name);
- console.log(" Designer colors:", appState.currentPattern?.designer_colors);
- console.log(" Layer labels:", appState.currentPattern?.layerLabels);
- console.log(" Layers array:", appState.currentPattern?.layers?.map((l, i) => `${i}: ${l.path?.split('/').pop()}`));
- // Check if this is a furniture collection
- const isFurnitureCollection = appState.selectedCollection?.wallMask != null ||
- appState.selectedCollection?.furnitureType != null;
- if (isFurnitureCollection) {
- appState.furnitureMode = true;
- }
- // โ Build layer + input models once pattern is set
- populateLayerInputs(pattern);
- // ===== DEBUG AFTER populateLayerInputs =====
- console.log("๐๏ธ UI POPULATION DEBUG:");
- console.log(" currentLayers count:", appState.currentLayers?.length);
- console.log(" currentLayers content:");
- appState.currentLayers?.forEach((layer, index) => {
- console.log(` ${index}: "${layer.label}" = "${layer.color}"`);
- });
- // ===== DEBUG ACTUAL DOM INPUTS =====
- setTimeout(() => {
- console.log("๐ ACTUAL UI INPUTS:");
- const inputs = document.querySelectorAll('.layer-input');
- inputs.forEach((input, index) => {
- const container = input.closest('.layer-input-container');
- const label = container?.querySelector('.layer-label')?.textContent;
- console.log(` UI Input ${index}: "${label}" = "${input.value}"`);
- });
- }, 100); // Small delay to ensure DOM is updated
- console.log(">>> Updated appState.currentPattern:", JSON.stringify(pattern, null, 2));
- appState.curatedColors = appState.selectedCollection.curatedColors || [];
- console.log(">>> Updated appState.curatedColors:", appState.curatedColors);
- if (!Array.isArray(appState.colorsData) || appState.colorsData.length === 0) {
- console.warn("๐ Sherwin-Williams colors not loaded yet. Delaying populateCuratedColors.");
- return;
- }
- // โ Only call curated color population when everything is ready
- if (appState.colorsData.length && collection.curatedColors?.length) {
- appState.curatedColors = collection.curatedColors;
- populateCuratedColors(appState.curatedColors);
- } else {
- console.warn("X Not populating curated colors - missing data");
- }
- const isFurniturePattern = appState.currentPattern?.isFurniture || false;
- updatePreview();
- // Check if we're in fabric mode - if so, only render fabric mockup
- if (appState.isInFabricMode) {
- console.log("๐งต loadPatternData in fabric mode - calling renderFabricMockup()");
- renderFabricMockup();
- } else {
- updateRoomMockup();
- }
- populatePatternThumbnails(appState.selectedCollection.patterns);
- populateCoordinates();
- } else {
- console.error(">>> Pattern not found:", patternId);
- }
- }
- // GUARD / TRACE WRAPPER
- if (USE_GUARD && DEBUG_TRACE) {
- loadPatternData = guard(traceWrapper(loadPatternData, "loadPatternData")); // Wrapped for debugging
- } else if (USE_GUARD) {
- loadPatternData = guard(loadPatternData, "loadPatternData"); // Wrapped for debugging
- }
- // Pattern scaling
- window.setPatternScale = function(multiplier) {
- appState.scaleMultiplier = multiplier;
- console.log(`>>> Scale multiplier set to: ${appState.scaleMultiplier}`);
- // Highlight active button
- document.querySelectorAll('#scaleControls button').forEach(btn => {
- const btnMultiplier = parseFloat(btn.dataset.multiplier);
- if (btnMultiplier === multiplier) {
- btn.classList.add('!bg-blue-500', 'text-white', 'active-scale');
- btn.classList.remove('!bg-gray-200');
- } else {
- btn.classList.add('!bg-gray-200');
- btn.classList.remove('!bg-blue-500', 'text-white', 'active-scale');
- }
- });
- // Check if we're in fabric mode - if so, only render fabric mockup
- if (appState.isInFabricMode) {
- console.log("๐งต setPatternScale in fabric mode - calling renderFabricMockup()");
- renderFabricMockup();
- } else {
- updateRoomMockup();
- }
- const isFurniturePattern = appState.currentPattern?.isFurniture || false;
- updatePreview();
- };
- // GUARD / TRACE WRAPPER
- if (USE_GUARD && DEBUG_TRACE) {
- setPatternScale = guard(traceWrapper(setPatternScale, "setPatternScale")); // Wrapped for debugging
- } else if (USE_GUARD) {
- setPatternScale = guard(setPatternScale, "setPatternScale"); // Wrapped for debugging
- }
- // Initialize scale on page load
- document.addEventListener('DOMContentLoaded', () => {
- appState.scaleMultiplier = 1; // Default to Normal
- setPatternScale(1);
- console.log('setPatternScale called with multiplier:', appState.scaleMultiplier);
- });
- // Ensure updatePreview is defined before updateDisplays uses it
- // ============================================================================
- // CORE ISSUES AND FIXES
- // ============================================================================
- // 1. Fix buildLayerModel to return a flat array consistently
- function buildLayerModel(pattern, designerColors = [], options = {}) {
- const { isWallPanel = false, tintWhite = false } = options;
- const patternLayers = pattern.layers || [];
- const layerLabels = pattern.layerLabels || [];
- console.log("๐๏ธ buildLayerModel LABEL FIX DEBUG:");
- console.log(" Pattern layers:", patternLayers.length);
- console.log(" Layer labels:", layerLabels);
- console.log(" Designer colors available:", designerColors.length);
- let colorIndex = 0;
- let inputIndex = 0;
- const allLayers = [];
- // Check if this is a furniture collection
- console.log("๐ FURNITURE DETECTION DEBUG:");
- console.log(" selectedCollection name:", appState.selectedCollection?.name);
- console.log(" selectedCollection wallMask:", appState.selectedCollection?.wallMask);
- console.log(" selectedCollection furnitureType:", appState.selectedCollection?.furnitureType);
- console.log(" selectedCollection keys:", Object.keys(appState.selectedCollection || {}));
- const isFurnitureCollection = appState.selectedCollection?.wallMask != null;
- console.log(" isFurnitureCollection result:", isFurnitureCollection);
- if (isFurnitureCollection) {
- // Add wall color layer
- const furnitureConfig = appState.selectedCollection?.furnitureConfig;
- const defaultWallColor = furnitureConfig?.defaultWallColor || "SW7006 Extra White";
- allLayers.push({
- label: "Wall Color",
- color: defaultWallColor,
- path: null,
- isBackground: false,
- isShadow: false,
- isWallPanel: false,
- inputId: `layer-${inputIndex++}`
- });
- console.log(` โ Added Wall Color (default): ${defaultWallColor}`);
- // Add sofa base layer
- allLayers.push({
- label: "BG/Sofa Base",
- color: designerColors[colorIndex++] || "Snowbound",
- path: null,
- isBackground: true,
- isShadow: false,
- isWallPanel: false,
- inputId: `layer-${inputIndex++}`
- });
- console.log(` โ Added BG/Sofa Base (designer color ${colorIndex - 1})`);
- } else {
- // Standard collection - just background
- allLayers.push({
- label: "Background",
- color: designerColors[colorIndex++] || "Snowbound",
- path: null,
- isBackground: true,
- isShadow: false,
- isWallPanel: false,
- inputId: `layer-${inputIndex++}`
- });
- }
- // โ PATTERN LAYERS (shared by both furniture and standard)
- console.log(" ๐จ Processing pattern layers:");
- let patternLabelIndex = 0;
- for (let i = 0; i < patternLayers.length; i++) {
- const layer = patternLayers[i];
- const isTrueShadow = layer.isShadow === true;
- if (!isTrueShadow) {
- const originalLabel = layerLabels[patternLabelIndex] || `Pattern Layer ${patternLabelIndex + 1}`;
- const layerObj = {
- label: originalLabel,
- color: designerColors[colorIndex++] || "Snowbound",
- path: layer.path || "",
- isBackground: false,
- isShadow: false,
- isWallPanel: false,
- tintWhite,
- inputId: `layer-${inputIndex++}`,
- patternLayerIndex: i
- };
- allLayers.push(layerObj);
- console.log(` โ Added pattern layer: "${originalLabel}" (designer color ${colorIndex - 1})`);
- patternLabelIndex++;
- } else {
- // Shadow layers (no input needed)
- const layerObj = {
- label: `Shadow ${i + 1}`,
- color: null,
- path: layer.path || "",
- isBackground: false,
- isShadow: true,
- isWallPanel: false,
- tintWhite,
- inputId: null,
- patternLayerIndex: i
- };
- allLayers.push(layerObj);
- console.log(` โ Added shadow layer: "Shadow ${i + 1}" (no color index used)`);
- }
- }
- console.log(`๐๏ธ Final layer model (used ${colorIndex} designer colors):`);
- allLayers.forEach((layer, index) => {
- const type = layer.isBackground ? 'bg' : layer.isShadow ? 'shadow' : 'layer';
- console.log(` ${index}: ${layer.label} (${type}) = ${layer.color || 'no color'}`);
- });
- // VALIDATION: Check counts
- const inputLayers = allLayers.filter(l => !l.isShadow);
- console.log(`โ Created ${inputLayers.length} input layers, used ${colorIndex} designer colors`);
- if (designerColors.length < colorIndex) {
- console.warn(`โ ๏ธ Not enough designer colors: need ${colorIndex}, have ${designerColors.length}`);
- }
- // Add this at the very end of buildLayerModel(), just before the return statement
- console.log(`๐๏ธ FINAL LAYER MODEL DEBUG:`);
- console.log(` Total layers created: ${allLayers.length}`);
- console.log(` isFurnitureCollection was: ${isFurnitureCollection}`);
- console.log(` Used ${colorIndex} designer colors`);
- console.log(` Final layer structure:`);
- allLayers.forEach((layer, index) => {
- const type = layer.isBackground ? 'bg' : layer.isShadow ? 'shadow' : 'input';
- console.log(` ${index}: "${layer.label}" (${type}) = "${layer.color}" | inputId: ${layer.inputId}`);
- });
- return allLayers;
- }
- // โ Wrap in an IIFE to avoid illegal top-level return
- if (appState.currentPattern) {
- (() => {
- try {
- const pattern = appState.currentPattern;
- if (!pattern || !Array.isArray(pattern.layers)) {
- console.error("โ Invalid pattern or missing layers:", pattern);
- return;
- }
- const designerColors = pattern.designer_colors || [];
- appState.currentLayers = buildLayerModel(
- pattern,
- designerColors,
- {
- isWallPanel: appState.selectedCollection?.name === "wall-panels",
- tintWhite: appState.tintWhite || false
- }
- );
- appState.layerInputs = appState.currentLayers.map(layer => {
- const layerData = createColorInput(
- layer.label,
- layer.inputId,
- layer.color,
- layer.isBackground
- );
- return {
- ...layerData,
- color: layer.color,
- hex: lookupColor(layer.color) || "#FFFFFF"
- };
- });
- } catch (e) {
- console.error("โ Error populating layer inputs:", e);
- }
- })();
- }
- // 2. updatePreview
- let updatePreview = async () => {
- console.log("๐ updatePreview PATTERN DEBUG:");
- console.log(" currentPattern name:", appState.currentPattern?.name);
- console.log(" currentPattern layers:", appState.currentPattern?.layers?.map(l => l.path?.split('/').pop()));
- console.log(" isFurnitureMode:", appState.furnitureMode);
- console.log(" selectedCollection name:", appState.selectedCollection?.name);
- if (!dom.preview) return console.error("preview not found in DOM");
- try {
- if (!dom.preview) return console.error("preview not found in DOM");
- if (!appState.currentPattern) return console.error("No current pattern selected");
- console.log("๐ updatePreview START");
- // Get responsive canvas size from CSS custom properties
- const computedStyle = getComputedStyle(document.documentElement);
- const canvasSize = parseInt(computedStyle.getPropertyValue('--preview-size').replace('px', '')) || 700;
- console.log("๐ฑ Canvas size from CSS:", canvasSize);
- const previewCanvas = document.createElement("canvas");
- const previewCtx = previewCanvas.getContext("2d", { willReadFrequently: true });
- previewCanvas.width = canvasSize;
- previewCanvas.height = canvasSize;
- // Check if this is a furniture collection
- const isFurnitureCollection = appState.selectedCollection?.wallMask != null;
- const layerMapping = getLayerMappingForPreview(isFurnitureCollection);
- console.log("๐ SOFA BASE DEBUG:");
- console.log(" Layer mapping:", layerMapping);
- console.log(" backgroundIndex:", layerMapping.backgroundIndex);
- console.log(" Current layers length:", appState.currentLayers.length);
- console.log("๐ Layer mapping:", layerMapping);
- console.log("๐ Current layers:", appState.currentLayers.map((l, i) => `${i}: ${l.label} = ${l.color}`));
- let patternToRender = appState.currentPattern;
- let usesBotanicalLayers = false;
- // For furniture collections, try to find the botanical pattern
- if (isFurnitureCollection) {
- console.log("๐ฟ Furniture mode detected - looking for original pattern");
- // Try multiple ways to get the original pattern
- let originalPattern = null;
- // Method 1: Check if furniture pattern stores original
- if (appState.currentPattern.originalPattern) {
- originalPattern = appState.currentPattern.originalPattern;
- console.log("โ Found original pattern via .originalPattern");
- }
- // Method 2: Look up by name in botanicals collection
- if (!originalPattern) {
- const botanicalCollection = appState.collections.find(c => c.name === "botanicals");
- if (botanicalCollection) {
- // Remove any furniture prefixes from the name to find botanical pattern
- const cleanPatternName = appState.currentPattern.name
- .replace(/^.*\s+/, '') // Remove collection prefix
- .replace(/\s+\w+\s+sofa$/i, ''); // Remove furniture suffix
- originalPattern = botanicalCollection.patterns.find(p =>
- p.name.toLowerCase() === cleanPatternName.toLowerCase() ||
- p.name.toLowerCase() === appState.currentPattern.name.toLowerCase()
- );
- if (originalPattern) {
- console.log("โ Found original pattern by name lookup:", originalPattern.name);
- }
- }
- }
- // Method 3: Use stored original collection
- if (!originalPattern && appState.originalCollection) {
- originalPattern = appState.originalCollection.patterns?.find(p =>
- p.id === appState.currentPattern.id
- );
- if (originalPattern) {
- console.log("โ Found original pattern via originalCollection");
- }
- }
- if (originalPattern) {
- console.log("๐ฟ Using original pattern for preview:", originalPattern.name);
- console.log(" Original layers:", originalPattern.layers?.map(l => l.path.split('/').pop()));
- patternToRender = originalPattern;
- usesBotanicalLayers = true;
- } else {
- console.warn("โ ๏ธ Could not find original pattern, using furniture pattern");
- }
- }
- // Get background color based on collection type
- let backgroundLayerIndex = layerMapping.backgroundIndex;
- let backgroundColor;
- if (isFurnitureCollection && usesBotanicalLayers) {
- // โ FIX: For furniture mode pattern preview, use the BG/Sofa Base color (index 1)
- // but this should be the same as the original background color
- backgroundColor = lookupColor(appState.currentLayers[1]?.color || "Snowbound");
- console.log(`๐ฟ Furniture mode pattern preview - using BG/Sofa Base color from input 1: ${backgroundColor}`);
- } else {
- // Standard mode or furniture room mockup
- const backgroundLayer = appState.currentLayers[backgroundLayerIndex];
- backgroundColor = lookupColor(backgroundLayer?.color || "Snowbound");
- console.log(`๐จ Standard background color from input ${backgroundLayerIndex}: ${backgroundColor}`);
- }
- console.log(`๐จ Background color from input ${backgroundLayerIndex}: ${backgroundColor}`);
- // Clear canvas to transparent
- previewCtx.clearRect(0, 0, previewCanvas.width, previewCanvas.height);
- // Handle tint white patterns
- if (patternToRender.tintWhite && patternToRender.baseComposite) {
- console.log("๐จ Rendering tint white pattern");
- const baseImage = new Image();
- baseImage.crossOrigin = "Anonymous";
- baseImage.src = normalizePath(patternToRender.baseComposite);
- await new Promise((resolve, reject) => {
- baseImage.onload = () => {
- const scaleMultiplier = appState.scaleMultiplier || 1;
- const imgAspect = baseImage.width / baseImage.height;
- const maxSize = canvasSize * scaleMultiplier;
- let drawWidth, drawHeight, offsetX, offsetY;
- if (imgAspect > 1) {
- drawWidth = Math.min(maxSize, canvasSize);
- drawHeight = drawWidth / imgAspect;
- } else {
- drawHeight = Math.min(maxSize, canvasSize);
- drawWidth = drawHeight * imgAspect;
- }
- offsetX = (canvasSize - drawWidth) / 2;
- offsetY = (canvasSize - drawHeight) / 2;
- previewCtx.fillStyle = backgroundColor;
- previewCtx.fillRect(offsetX, offsetY, drawWidth, drawHeight);
- previewCtx.drawImage(baseImage, offsetX, offsetY, drawWidth, drawHeight);
- // Apply tint to white areas
- let imageData;
- try {
- imageData = previewCtx.getImageData(offsetX, offsetY, drawWidth, drawHeight);
- } catch (e) {
- console.warn("โ ๏ธ Canvas tainted, skipping preview tinting:", e.message);
- resolve();
- return;
- }
- const data = imageData.data;
- const wallColor = lookupColor(appState.currentLayers[0]?.color || "Snowbound");
- const hex = wallColor.replace("#", "");
- const rTint = parseInt(hex.substring(0, 2), 16);
- const gTint = parseInt(hex.substring(2, 4), 16);
- const bTint = parseInt(hex.substring(4, 6), 16);
- for (let i = 0; i < data.length; i += 4) {
- const r = data[i], g = data[i + 1], b = data[i + 2];
- if (r > 240 && g > 240 && b > 240) {
- data[i] = rTint;
- data[i + 1] = gTint;
- data[i + 2] = bTint;
- }
- }
- previewCtx.putImageData(imageData, offsetX, offsetY);
- resolve();
- };
- baseImage.onerror = reject;
- });
- } else if (patternToRender.layers?.length) {
- console.log("๐จ Rendering layered pattern");
- console.log(" Uses botanical layers:", usesBotanicalLayers);
- const firstLayer = patternToRender.layers.find(l => !l.isShadow);
- if (firstLayer) {
- const tempImg = new Image();
- tempImg.crossOrigin = "Anonymous";
- tempImg.src = normalizePath(firstLayer.path);
- await new Promise((resolve) => {
- tempImg.onload = () => {
- const patternAspect = tempImg.width / tempImg.height;
- const scaleMultiplier = appState.scaleMultiplier || 1;
- let patternDisplayWidth, patternDisplayHeight;
- const baseSize = canvasSize;
- if (patternAspect > 1) {
- patternDisplayWidth = Math.min(baseSize, canvasSize);
- patternDisplayHeight = patternDisplayWidth / patternAspect;
- } else {
- patternDisplayHeight = Math.min(baseSize, canvasSize);
- patternDisplayWidth = patternDisplayHeight * patternAspect;
- }
- const offsetX = (canvasSize - patternDisplayWidth) / 2;
- const offsetY = (canvasSize - patternDisplayHeight) / 2;
- previewCtx.fillStyle = backgroundColor;
- previewCtx.fillRect(offsetX, offsetY, patternDisplayWidth, patternDisplayHeight);
- console.log(`๐จ Pattern area: ${patternDisplayWidth.toFixed(0)}x${patternDisplayHeight.toFixed(0)}`);
- resolve({ offsetX, offsetY, patternDisplayWidth, patternDisplayHeight, scaleMultiplier });
- };
- tempImg.onerror = () => resolve(null);
- }).then(async (patternBounds) => {
- if (!patternBounds) return;
- // Render each layer with correct color mapping
- for (let layerIndex = 0; layerIndex < patternToRender.layers.length; layerIndex++) {
- const layer = patternToRender.layers[layerIndex];
- const isShadow = layer.isShadow === true;
- let layerColor = null;
- if (!isShadow) {
- if (usesBotanicalLayers) {
- // โ FIX: Map botanical layer to furniture input correctly
- const furnitureInputIndex = layerMapping.patternStartIndex + layerIndex;
- layerColor = lookupColor(appState.currentLayers[furnitureInputIndex]?.color || "Snowbound");
- // โ DEBUG: Show the mapping
- const inputLayer = appState.currentLayers[furnitureInputIndex];
- console.log(`๐ฟ Botanical layer ${layerIndex} โ furniture input ${furnitureInputIndex} (${inputLayer?.label}) โ ${layerColor}`);
- } else {
- // Standard mapping
- const inputIndex = layerMapping.patternStartIndex + layerIndex;
- layerColor = lookupColor(appState.currentLayers[inputIndex]?.color || "Snowbound");
- console.log(`๐ Standard layer ${layerIndex} โ input ${inputIndex} โ ${layerColor}`);
- }
- }
- await new Promise((resolve) => {
- processImage(layer.path, (processedCanvas) => {
- if (!(processedCanvas instanceof HTMLCanvasElement)) {
- return resolve();
- }
- const patternSize = Math.max(processedCanvas.width, processedCanvas.height);
- const baseScale = patternBounds.patternDisplayWidth / patternSize;
- const finalScale = baseScale * patternBounds.scaleMultiplier;
- const tileWidth = processedCanvas.width * finalScale;
- const tileHeight = processedCanvas.height * finalScale;
- const tilingType = patternToRender.tilingType || "";
- const isHalfDrop = tilingType === "half-drop";
- previewCtx.save();
- previewCtx.beginPath();
- previewCtx.rect(
- patternBounds.offsetX,
- patternBounds.offsetY,
- patternBounds.patternDisplayWidth,
- patternBounds.patternDisplayHeight
- );
- previewCtx.clip();
- previewCtx.globalCompositeOperation = isShadow ? "multiply" : "source-over";
- previewCtx.globalAlpha = isShadow ? 0.3 : 1.0;
- const startX = patternBounds.offsetX;
- const startY = patternBounds.offsetY;
- const endX = patternBounds.offsetX + patternBounds.patternDisplayWidth + tileWidth;
- const endY = patternBounds.offsetY + patternBounds.patternDisplayHeight + tileHeight;
- for (let x = startX; x < endX; x += tileWidth) {
- const isOddColumn = Math.floor((x - startX) / tileWidth) % 2 !== 0;
- const yOffset = isHalfDrop && isOddColumn ? tileHeight / 2 : 0;
- for (let y = startY - tileHeight + yOffset; y < endY; y += tileHeight) {
- previewCtx.drawImage(processedCanvas, x, y, tileWidth, tileHeight);
- }
- }
- previewCtx.restore();
- console.log(`โ Rendered layer ${layerIndex} with color ${layerColor}`);
- resolve();
- }, layerColor, 2.2, isShadow, false, false);
- });
- }
- });
- }
- }
- // Update DOM
- dom.preview.innerHTML = "";
- dom.preview.appendChild(previewCanvas);
- dom.preview.style.width = `${canvasSize}px`;
- dom.preview.style.height = `${canvasSize}px`;
- dom.preview.style.backgroundColor = "rgba(17, 24, 39, 1)";
- if (patternToRender.name) {
- dom.patternName.textContent = toInitialCaps(appState.currentPattern.name); // Keep original name
- }
- console.log("โ Pattern preview rendered");
- } catch (err) {
- console.error("updatePreview error:", err);
- }
- };
- // Utility: Promisified image loader
- function loadImage(src) {
- return new Promise((resolve, reject) => {
- if (!src) {
- console.error("โ loadImage: No src provided");
- reject(new Error("No image source provided"));
- return;
- }
- // Normalize the path to fix ./data/ vs data/ inconsistencies
- const normalizedSrc = normalizePath(src);
- console.log(`๐ฅ Loading image: ${src} -> ${normalizedSrc}`);
- const img = new Image();
- img.crossOrigin = "Anonymous";
- img.onload = () => {
- console.log(`โ Image loaded successfully: ${normalizedSrc} (${img.naturalWidth}x${img.naturalHeight})`);
- resolve(img);
- };
- img.onerror = (error) => {
- console.error(`โ Failed to load image: ${normalizedSrc}`);
- console.error("โ Error details:", error);
- reject(new Error(`Failed to load image: ${normalizedSrc}`));
- };
- img.src = normalizedSrc;
- });
- }
- // room mockup
- let updateRoomMockup = async () => {
- try {
- if (!dom.roomMockup) {
- console.error("roomMockup element not found in DOM");
- return;
- }
- if (!appState.selectedCollection || !appState.currentPattern) {
- console.log("๐ Skipping updateRoomMockup - no collection/pattern selected");
- return;
- }
- // Check if this is a furniture collection
- const isFurnitureCollection = appState.selectedCollection.wallMask != null;
- if (isFurnitureCollection) {
- console.log("๐ช Rendering furniture preview");
- updateFurniturePreview();
- return;
- }
- const isWallPanel = appState.selectedCollection?.name === "wall-panels";
- // ๐ ADD THIS DEBUG HERE:
- console.log("๐ CURRENT LAYERS MAPPING (Room Mockup):");
- appState.currentLayers.forEach((layer, index) => {
- console.log(` ${index}: ${layer.label} = ${layer.color} (isShadow: ${layer.isShadow})`);
- });
- // ๐ DEBUG: Check what path we're taking
- console.log("๐ DEBUG START updateRoomMockup");
- console.log("๐ isWallPanel:", isWallPanel);
- console.log("๐ selectedCollection name:", appState.selectedCollection?.name);
- console.log("๐ currentPattern.isWallPanel:", appState.currentPattern?.isWallPanel);
- console.log("๐ currentPattern has layers:", !!appState.currentPattern?.layers?.length);
- console.log("๐ currentPattern has tintWhite:", !!appState.currentPattern?.tintWhite);
- // Get colors from correct layer indices
- const wallColor = isWallPanel ?
- lookupColor(appState.currentLayers[0]?.color || "Snowbound") :
- lookupColor(appState.currentLayers[0]?.color || "Snowbound");
- const backgroundColor = isWallPanel ?
- lookupColor(appState.currentLayers[1]?.color || "Snowbound") :
- lookupColor(appState.currentLayers[0]?.color || "Snowbound");
- console.log(">>> Wall color:", wallColor, "Background color:", backgroundColor);
- const canvas = document.createElement("canvas");
- const ctx = canvas.getContext("2d");
- canvas.width = 600;
- canvas.height = 450;
- console.log(`๐จ Room mockup canvas created: ${canvas.width}x${canvas.height}`);
- const processOverlay = async () => {
- console.log("๐ processOverlay() START");
- // Fill wall color
- ctx.fillStyle = wallColor;
- ctx.fillRect(0, 0, canvas.width, canvas.height);
- console.log("๐ Wall color filled");
- if (isWallPanel && appState.currentPattern?.layers?.length) {
- console.log("๐ TAKING PATH: Wall panel processing");
- // Handle wall panel rendering
- const panelWidthInches = appState.currentPattern.size[0] || 24;
- const panelHeightInches = appState.currentPattern.size[1] || 36;
- const scale = Math.min(canvas.width / 100, canvas.height / 80) * (appState.scaleMultiplier || 1);
- const panelWidth = panelWidthInches * scale;
- const panelHeight = panelHeightInches * scale;
- const layout = appState.currentPattern.layout || "3,20";
- const [numPanelsStr, spacingStr] = layout.split(",");
- const numPanels = parseInt(numPanelsStr, 10) || 3;
- const spacing = parseInt(spacingStr, 10) || 20;
- const totalWidth = (numPanels * panelWidth) + ((numPanels - 1) * spacing);
- const startX = (canvas.width - totalWidth) / 2;
- const startY = (canvas.height - panelHeight) / 2 - (appState.currentPattern?.verticalOffset || 50);
- // Create panel canvas
- const panelCanvas = document.createElement("canvas");
- panelCanvas.width = panelWidth;
- panelCanvas.height = panelHeight;
- const panelCtx = panelCanvas.getContext("2d");
- // Process panel layers - find input layers only
- let currentLayerIndex = 0; // Start from first design layer
- for (let i = 0; i < appState.currentPattern.layers.length; i++) {
- const layer = appState.currentPattern.layers[i];
- const isShadow = layer.isShadow === true;
- console.log(`๐ LAYER DEBUG ${i}:`, {
- path: layer.path,
- isShadow: isShadow,
- currentLayerIndex: currentLayerIndex,
- expectedColorIndex: currentLayerIndex + 2
- });
- let layerColor = null;
- if (!isShadow) {
- // Wall panels: [0: Wall, 1: Background, 2+: Design layers]
- const colorLayerIndex = currentLayerIndex + 2; // Skip wall (0) and background (1)
- layerColor = lookupColor(appState.currentLayers[colorLayerIndex]?.color || "Snowbound");
- console.log(`๐จ Regular layer ${i}: using color from currentLayers[${colorLayerIndex}] = ${layerColor}`);
- currentLayerIndex++;
- }
- await new Promise((resolve) => {
- console.log(`๐ About to call processImage with: isShadow=${isShadow}, isWallPanel=true, isWall=false`);
- processImage(layer.path, (processedCanvas) => {
- if (processedCanvas instanceof HTMLCanvasElement) {
- panelCtx.globalCompositeOperation = isShadow ? "multiply" : "source-over";
- panelCtx.globalAlpha = isShadow ? 0.3 : 1.0;
- panelCtx.drawImage(processedCanvas, 0, 0, panelWidth, panelHeight);
- }
- resolve();
- }, layerColor, 2.2, isShadow, true, false);
- });
- }
- // Draw panels
- for (let i = 0; i < numPanels; i++) {
- const x = startX + (i * (panelWidth + spacing));
- ctx.fillStyle = backgroundColor;
- ctx.fillRect(x, startY, panelWidth, panelHeight);
- ctx.drawImage(panelCanvas, x, startY, panelWidth, panelHeight);
- }
- } else {
- console.log("๐ TAKING PATH: Regular pattern processing");
- console.log("๐ appState.currentPattern:", appState.currentPattern);
- console.log("๐ appState.currentPattern.layers:", appState.currentPattern?.layers);
- // Handle regular pattern rendering
- const patternCanvas = document.createElement("canvas");
- patternCanvas.width = canvas.width;
- patternCanvas.height = canvas.height;
- const patternCtx = patternCanvas.getContext("2d");
- if (appState.currentPattern?.tintWhite && appState.currentPattern?.baseComposite) {
- console.log("๐ TAKING SUBPATH: Tint white");
- // Handle tint white in room mockup
- const baseImage = new Image();
- baseImage.src = normalizePath(appState.currentPattern.baseComposite);
- await new Promise((resolve) => {
- baseImage.onload = () => {
- const scale = (appState.currentScale / 100 || 1) * (appState.scaleMultiplier || 1);
- const tileWidth = baseImage.width * scale;
- const tileHeight = baseImage.height * scale;
- // Tile pattern
- for (let x = -tileWidth; x < canvas.width + tileWidth; x += tileWidth) {
- for (let y = -tileHeight; y < canvas.height + tileHeight; y += tileHeight) {
- patternCtx.drawImage(baseImage, x, y, tileWidth, tileHeight);
- }
- }
- // Apply tint (with CORS protection)
- let imageData;
- try {
- imageData = patternCtx.getImageData(0, 0, canvas.width, canvas.height);
- } catch (e) {
- console.warn("โ ๏ธ Canvas tainted, skipping tint white effect:", e.message);
- ctx.drawImage(patternCanvas, 0, 0);
- return;
- }
- const data = imageData.data;
- const hex = wallColor.replace("#", "");
- const rTint = parseInt(hex.substring(0, 2), 16);
- const gTint = parseInt(hex.substring(2, 4), 16);
- const bTint = parseInt(hex.substring(4, 6), 16);
- for (let i = 0; i < data.length; i += 4) {
- const r = data[i], g = data[i + 1], b = data[i + 2];
- if (r > 240 && g > 240 && b > 240) {
- data[i] = rTint;
- data[i + 1] = gTint;
- data[i + 2] = bTint;
- }
- }
- patternCtx.putImageData(imageData, 0, 0);
- ctx.drawImage(patternCanvas, 0, 0);
- resolve();
- };
- baseImage.onerror = resolve;
- });
- } else if (appState.currentPattern?.layers?.length && !isWallPanel) {
- console.log("๐ TAKING SUBPATH: Regular layers");
- // Handle regular layered patterns - FIXED indexing
- let currentLayerIndex = 0; // Start from first non-shadow layer
- const inputLayers = appState.currentLayers.filter(layer => !layer.isShadow);
- let inputLayerIndex = 0;
- for (let i = 0; i < appState.currentPattern.layers.length; i++) {
- const layer = appState.currentPattern.layers[i];
- const isShadow = layer.isShadow === true;
- let layerColor = null;
- if (!isShadow) {
- const inputLayer = inputLayers[inputLayerIndex + 1]; // Skip background
- layerColor = lookupColor(inputLayer?.color || "Snowbound");
- inputLayerIndex++; // Increment here
- }
- // Check for half-drop tiling (declare once, outside)
- const tilingType = appState.currentPattern.tilingType || "";
- const isHalfDrop = tilingType === "half-drop";
- console.log(`๐ ROOM MOCKUP Tiling type: ${tilingType}, Half-drop: ${isHalfDrop}`);
- await new Promise((resolve) => {
- processImage(layer.path, (processedCanvas) => {
- if (processedCanvas instanceof HTMLCanvasElement) {
- const scale = (appState.currentScale / 100 || 1) * (appState.scaleMultiplier || 1);
- const tileWidth = processedCanvas.width * scale;
- const tileHeight = processedCanvas.height * scale;
- patternCtx.globalCompositeOperation = isShadow ? "multiply" : "source-over";
- patternCtx.globalAlpha = isShadow ? 0.3 : 1.0;
- for (let x = -tileWidth; x < canvas.width + tileWidth; x += tileWidth) {
- const isOddColumn = Math.floor((x + tileWidth) / tileWidth) % 2 !== 0;
- const yOffset = isHalfDrop && isOddColumn ? tileHeight / 2 : 0;
- console.log(`๐ Column at x=${x}, isOdd=${isOddColumn}, yOffset=${yOffset}`);
- for (let y = -tileHeight + yOffset; y < canvas.height + tileHeight; y += tileHeight) {
- patternCtx.drawImage(processedCanvas, x, y, tileWidth, tileHeight);
- }
- }
- console.log(`โ Regular layer ${i} rendered with color ${layerColor}`);
- }
- resolve();
- }, layerColor, 2.2, isShadow, false, false);
- });
- }
- ctx.drawImage(patternCanvas, 0, 0);
- console.log("๐ Pattern canvas drawn to main canvas");
- }
- }
- console.log("๐ Finished pattern processing, moving to collection mockup check");
- console.log("๐ Full selectedCollection:", Object.keys(appState.selectedCollection));
- console.log("๐ selectedCollection object:", appState.selectedCollection);
- console.log("๐ selectedCollection.mockup:", appState.selectedCollection?.mockup);
- console.log("๐ selectedCollection.mockupShadow:", appState.selectedCollection?.mockupShadow);
- // Apply mockup overlay if exists
- if (appState.selectedCollection?.mockup) {
- const originalPath = appState.selectedCollection.mockup;
- const normalizedPath = normalizePath(originalPath);
- console.log(`๐ Loading collection mockup:`);
- console.log(` Original: ${originalPath}`);
- console.log(` Normalized: ${normalizedPath}`);
- const mockupImage = new Image();
- mockupImage.crossOrigin = "Anonymous";
- mockupImage.src = normalizedPath;
- await new Promise((resolve) => {
- mockupImage.onload = () => {
- console.log(`โ Collection mockup loaded: ${mockupImage.width}x${mockupImage.height}`);
- const fit = scaleToFit(mockupImage, canvas.width, canvas.height);
- ctx.drawImage(mockupImage, fit.x, fit.y, fit.width, fit.height);
- console.log(`๐ Mockup drawn at: ${fit.x}, ${fit.y}, ${fit.width}x${fit.height}`);
- console.log("๐ selectedCollection:", appState.selectedCollection?.name);
- console.log("๐ selectedCollection.elements:", appState.selectedCollection?.elements);
- resolve();
- };
- mockupImage.onerror = (e) => {
- console.error(`โ Failed to load collection mockup: ${normalizedPath}`, e);
- console.error(`โ Actual URL that failed: ${mockupImage.src}`);
- resolve();
- };
- });
- }
- // Apply shadow overlay if exists
- if (appState.selectedCollection?.mockupShadow) {
- const shadowOriginalPath = appState.selectedCollection.mockupShadow;
- const shadowNormalizedPath = normalizePath(shadowOriginalPath);
- console.log(`๐ซ๏ธ Loading collection shadow:`);
- console.log(` Original: ${shadowOriginalPath}`);
- console.log(` Normalized: ${shadowNormalizedPath}`);
- const shadowOverlay = new Image();
- shadowOverlay.crossOrigin = "Anonymous";
- shadowOverlay.src = shadowNormalizedPath;
- await new Promise((resolve) => {
- shadowOverlay.onload = () => {
- console.log(`โ Collection shadow loaded: ${shadowOverlay.width}x${shadowOverlay.height}`);
- ctx.globalCompositeOperation = "multiply";
- const fit = scaleToFit(shadowOverlay, canvas.width, canvas.height);
- ctx.drawImage(shadowOverlay, fit.x, fit.y, fit.width, fit.height);
- console.log(`๐ซ๏ธ Shadow drawn at: ${fit.x}, ${fit.y}, ${fit.width}x${fit.height}`);
- ctx.globalCompositeOperation = "source-over";
- resolve();
- };
- shadowOverlay.onerror = (e) => {
- console.error(`โ Failed to load shadow overlay: ${shadowNormalizedPath}`, e);
- console.error(`โ Actual shadow URL that failed: ${shadowOverlay.src}`);
- resolve();
- };
- });
- } else {
- console.warn("โ ๏ธ No mockup found for collection:", appState.selectedCollection?.name);
- console.log("๐ Available collection properties:", Object.keys(appState.selectedCollection || {}));
- }
- // Render final canvas with CORS error handling
- let dataUrl;
- try {
- dataUrl = canvas.toDataURL("image/png");
- console.log("โ Room mockup canvas exported successfully");
- } catch (e) {
- if (e.name === 'SecurityError') {
- console.log("๐ก๏ธ Room mockup CORS error - using canvas directly in DOM");
- // Instead of using dataURL, append the canvas directly
- canvas.style.cssText = "width: 100%; height: 100%; object-fit: contain; border: 1px solid #333;";
- dom.roomMockup.innerHTML = "";
- dom.roomMockup.appendChild(canvas);
- console.log("โ Room mockup canvas appended directly to DOM");
- ensureButtonsAfterUpdate();
- // Reset all styling including background from fabric mode
- dom.roomMockup.style.cssText = "width: 600px; height: 450px; position: relative; background-image: none; background-color: #434341;";
- return; // Exit early, don't create img element
- }
- throw e; // Re-throw non-CORS errors
- }
- const img = document.createElement("img");
- img.src = dataUrl;
- img.style.cssText = "width: 100%; height: 100%; object-fit: contain; border: 1px solid #333;";
- img.onload = () => {
- console.log("โ Room mockup image loaded successfully");
- };
- img.onerror = (e) => {
- console.error("โ Room mockup image failed to load:", e);
- };
- dom.roomMockup.innerHTML = "";
- dom.roomMockup.appendChild(img);
- console.log("โ Room mockup image appended to DOM");
- ensureButtonsAfterUpdate();
- dom.roomMockup.style.cssText = "width: 600px; height: 450px; position: relative; background: #434341;";
- };
- await processOverlay().catch(error => {
- console.error("Error processing room mockup:", error);
- });
- } catch (e) {
- console.error('Error in updateRoomMockup:', e);
- }
- };
- // GUARD / TRACE WRAPPER
- if (USE_GUARD && DEBUG_TRACE) {
- updateRoomMockup = guard(traceWrapper(updateRoomMockup, "updateRoomMockup")); // Wrapped for debugging
- } else if (USE_GUARD) {
- updateRoomMockup = guard(updateRoomMockup, "updateRoomMockup"); // Wrapped for debugging
- }
- const updateFurniturePreview = async () => {
- // Add this at the start of updateFurniturePreview()
- const layerMapping = getLayerMappingForPreview(true);
- console.log("๐ LAYER MAPPING DEBUG IN FURNITURE PREVIEW:");
- console.log(" wallIndex:", layerMapping.wallIndex);
- console.log(" backgroundIndex:", layerMapping.backgroundIndex);
- console.log(" patternStartIndex:", layerMapping.patternStartIndex);
- console.log(" Expected: wallIndex=0, backgroundIndex=1, patternStartIndex=2");
- try {
- console.log("๐๏ธ =========================");
- console.log("๐๏ธ Starting furniture preview");
- console.log("๐๏ธ =========================");
- const frozenZoomState = {
- scale: furnitureViewSettings.scale,
- offsetX: furnitureViewSettings.offsetX,
- offsetY: furnitureViewSettings.offsetY,
- isZoomed: furnitureViewSettings.isZoomed,
- timestamp: Date.now()
- };
- console.log("๐ FROZEN zoom state for all layers:", frozenZoomState);
- // ๐ ADD THIS DEBUG LINE:
- console.log("๐ ENTRY POINT - Current furnitureViewSettings:", JSON.stringify(furnitureViewSettings, null, 2));
- // โ PRESERVE ZOOM SETTINGS ONCE AT THE START
- const preservedSettings = {
- scale: furnitureViewSettings.scale,
- offsetX: furnitureViewSettings.offsetX,
- offsetY: furnitureViewSettings.offsetY,
- isZoomed: furnitureViewSettings.isZoomed
- };
- console.log("๐ Preserved zoom settings:", preservedSettings);
- // Basic validation
- if (!dom.roomMockup) {
- console.error("โ roomMockup element not found in DOM");
- return;
- }
- if (!appState.currentPattern) {
- console.error("โ No current pattern selected");
- return;
- }
- // Ensure furniture config is loaded
- if (!furnitureConfig) {
- console.log("๐ Loading furniture config...");
- await loadFurnitureConfig();
- }
- if (!furnitureConfig) {
- console.error("โ furnitureConfig still not loaded after attempt");
- return;
- }
- // Setup canvas
- const canvas = document.createElement("canvas");
- canvas.width = 600;
- canvas.height = 450;
- const ctx = canvas.getContext("2d");
- // Get collection and pattern data
- const collection = appState.selectedCollection;
- const pattern = appState.currentPattern;
- const furnitureType = collection?.furnitureType || 'sofa-capitol';
- const furniture = furnitureConfig?.[furnitureType];
- // Debug furniture config
- console.log("๐ FURNITURE CONFIG DEBUG:");
- console.log(" Collection name:", collection?.name);
- console.log(" Furniture type:", furnitureType);
- console.log(" Available furniture configs:", Object.keys(furnitureConfig || {}));
- console.log(" Selected furniture config exists:", !!furniture);
- if (!furniture) {
- console.error("โ No furniture config found for:", furnitureType);
- console.log("Available configs:", Object.keys(furnitureConfig));
- return;
- }
- // Debug furniture paths
- console.log("๐ FURNITURE PATHS DEBUG:");
- console.log(" Mockup path:", furniture.mockup);
- console.log(" Wall mask path:", furniture.wallMask);
- console.log(" Base path:", furniture.base);
- console.log(" Extras path:", furniture.extras);
- // Test if files exist
- const testPaths = [
- { name: "mockup", path: furniture.mockup },
- { name: "wallMask", path: furniture.wallMask },
- { name: "base", path: furniture.base },
- { name: "extras", path: furniture.extras }
- ];
- console.log("๐ TESTING FILE EXISTENCE:");
- testPaths.forEach(({ name, path }) => {
- if (path) {
- const testImg = new Image();
- testImg.onload = () => console.log(`โ ${name} file exists: ${path}`);
- testImg.onerror = () => console.log(`โ ${name} file MISSING: ${path}`);
- testImg.src = normalizePath(path);
- } else {
- console.log(`โ ๏ธ ${name} path not defined in config`);
- }
- });
- // Get layer mapping for furniture collection
- const layerMapping = getLayerMappingForPreview(true); // Always true for furniture
- console.log("๐ LAYER MAPPING DEBUG:");
- console.log(" Layer mapping:", layerMapping);
- console.log(" Total current layers:", appState.currentLayers.length);
- // Debug current layer assignments
- console.log("๐ CURRENT LAYER ASSIGNMENTS:");
- appState.currentLayers.forEach((layer, index) => {
- let usage = "unused";
- if (index === layerMapping.wallIndex) usage = "wall color";
- else if (index === layerMapping.backgroundIndex) usage = "sofa base color";
- else if (index >= layerMapping.patternStartIndex) usage = `pattern layer ${index - layerMapping.patternStartIndex}`;
- console.log(` ${index}: ${layer.label} = "${layer.color}" (${usage})`);
- });
- // Clear canvas with white background
- ctx.fillStyle = "transparent";
- ctx.fillRect(0, 0, 600, 450);
- console.log("๐งน Canvas cleared with white background");
- ctx.clearRect(0, 0, 600, 450);
- ctx.fillStyle = "#F5F5F5";
- ctx.fillRect(0, 0, 600, 450);
- // โ REMOVED: The problematic settings update that was resetting zoom
- // NO LONGER UPDATING furnitureViewSettings here - using preserved settings
- console.log("๐ FURNITURE VIEW SETTINGS:");
- console.log(" Scale:", furnitureViewSettings.scale);
- console.log(" Offset X:", furnitureViewSettings.offsetX);
- console.log(" Offset Y:", furnitureViewSettings.offsetY);
- try {
- console.log("๐๏ธ =========================");
- console.log("๐๏ธ FURNITURE RENDERING SEQUENCE (WITH WALL MASK)");
- console.log("๐๏ธ =========================");
- // ===== STEP 1: Draw room mockup base =====
- console.log("1๏ธโฃ Drawing mockup base (room scene)");
- const mockupPath = furniture.mockup;
- if (mockupPath) {
- console.log(" Mockup path:", mockupPath);
- await drawFurnitureLayer(ctx, mockupPath).catch(err => {
- console.error("โ Failed to load mockup:", err);
- zoomState: frozenZoomState
- ctx.fillStyle = "#E5E7EB";
- ctx.fillRect(0, 0, 600, 450);
- console.log("๐ Drew fallback background due to mockup failure");
- });
- console.log("โ Room mockup base drawn");
- } else {
- console.error("โ No mockup path in furniture config");
- ctx.fillStyle = "#E5E7EB";
- ctx.fillRect(0, 0, 600, 450);
- }
- // ===== STEP 2: Draw wall color using wall mask =====
- console.log("2๏ธโฃ Drawing wall color via mask");
- const wallColor = resolveColor(appState.currentLayers[layerMapping.wallIndex]?.color || "Snowbound");
- console.log(` Wall color from input ${layerMapping.wallIndex}: ${wallColor}`);
- if (furniture.wallMask) {
- console.log(" Wall mask path:", furniture.wallMask);
- await drawFurnitureLayer(ctx, furniture.wallMask, {
- tintColor: wallColor,
- isMask: true,
- zoomState: frozenZoomState
- });
- console.log("โ Wall color applied via mask");
- } else {
- console.error("โ No wallMask path in furniture config");
- console.log(" Available furniture config keys:", Object.keys(furniture));
- }
- // TEST: Try to load the wall mask image manually
- console.log("๐งช TESTING WALL MASK IMAGE LOAD:");
- try {
- const testMaskImg = new Image();
- testMaskImg.onload = () => {
- console.log(`โ Wall mask loaded successfully: ${furniture.wallMask}`);
- console.log(` Dimensions: ${testMaskImg.naturalWidth}x${testMaskImg.naturalHeight}`);
- console.log(` Image appears valid for masking`);
- };
- testMaskImg.onerror = (err) => {
- console.log(`โ Wall mask failed to load: ${furniture.wallMask}`);
- console.log(` Error:`, err);
- console.log(` This is why wall color fills entire canvas!`);
- };
- testMaskImg.src = normalizePath(furniture.wallMask);
- } catch (e) {
- console.log(`โ Error testing wall mask: ${e.message}`);
- }
- // ===== STEP 3: Draw sofa base =====
- console.log("3๏ธโฃ Drawing sofa base - USING MAPPING");
- // โ Use the layer mapping to get the correct background index
- const backgroundIndex = layerMapping.backgroundIndex;
- const backgroundLayer = appState.currentLayers[backgroundIndex];
- const sofaBaseColor = resolveColor(backgroundLayer?.color || "#FAFAFA");
- // โ ENHANCED DEBUG - Let's catch the bug red-handed
- console.log("๐ SOFA BASE COLOR RESOLUTION DEBUG:");
- console.log(" backgroundIndex:", backgroundIndex);
- console.log(" backgroundLayer:", backgroundLayer);
- console.log(" backgroundLayer.label:", backgroundLayer?.label);
- console.log(" backgroundLayer.color:", backgroundLayer?.color);
- console.log(" sofaBaseColor resolved to:", sofaBaseColor);
- // โ ALSO CHECK: What does resolveColor actually return?
- console.log(" resolveColor direct test:", resolveColor(backgroundLayer?.color));
- console.log(" lookupColor direct test:", lookupColor(backgroundLayer?.color));
- console.log(` Sofa base color from input ${backgroundIndex} (${appState.currentLayers[backgroundIndex]?.label}): ${sofaBaseColor}`);
- if (furniture.base) {
- console.log(" ๐๏ธ Sofa base path exists:", furniture.base);
- console.log(" ๐๏ธ Calling drawFurnitureLayer for sofa base...");
- // โ ENSURE SOFA BASE COMPLETES BEFORE PATTERNS
- console.log("๐ ABOUT TO DRAW SOFA BASE:");
- console.log(" furniture.base path:", furniture.base);
- console.log(" Should be: data/furniture/sofa-capitol/sofa-capitol-base.png");
- console.log(" Tint color:", sofaBaseColor);
- try {
- await drawFurnitureLayer(ctx, furniture.base, {
- tintColor: sofaBaseColor,
- zoomState: frozenZoomState
- });
- console.log(" โ Sofa base step completed - CONFIRMED");
- } catch (error) {
- console.error(" โ Sofa base failed:", error);
- }
- // โ Then: Add shadow layer with multiply blend (no UI input needed)
- const shadowPath = furniture.baseShadow || furniture.base.replace('.png', '-shadow.png');
- console.log(" ๐ Adding sofa base shadow...");
- await drawFurnitureLayer(ctx, shadowPath, {
- tintColor: null, // No tinting for shadow
- zoomState: frozenZoomState,
- blendMode: "multiply", // Multiply blend for shadow
- opacity: 0.7 // Adjust shadow intensity
- });
- console.log(" โ Sofa base shadow completed");
- } else {
- console.error("โ No base path in furniture config");
- }
- // โ ADD DELAY TO ENSURE SOFA BASE IS FULLY RENDERED
- console.log("โณ Waiting for sofa base to complete before patterns...");
- await new Promise(resolve => setTimeout(resolve, 50));
- // ===== STEP 4: Draw pattern layers =====
- console.log("4๏ธโฃ Drawing pattern layers - ENHANCED DEBUG");
- console.log(` Total pattern layers to process: ${pattern.layers.length}`);
- console.log(` Pattern layer start index: ${layerMapping.patternStartIndex}`);
- console.log(` Available inputs: ${appState.currentLayers.length}`);
- // Show all current inputs
- console.log(" ๐ ALL CURRENT INPUTS:");
- appState.currentLayers.forEach((layer, idx) => {
- console.log(` Input ${idx}: ${layer.label} = "${layer.color}"`);
- });
- console.log(" ๐จ PATTERN LAYER MAPPING:");
- for (let i = 0; i < pattern.layers.length; i++) {
- const layer = pattern.layers[i];
- const furnitureInputIndex = layerMapping.patternStartIndex + i;
- const inputLayer = appState.currentLayers[furnitureInputIndex];
- const layerColor = resolveColor(inputLayer?.color || "Snowbound");
- console.log(` ๐ Pattern layer ${i}:`);
- console.log(` Layer path: ${layer.path?.split('/').pop()}`);
- console.log(` Maps to input ${furnitureInputIndex}: ${inputLayer?.label} = "${inputLayer?.color}"`);
- console.log(` Resolved color: ${layerColor}`);
- console.log(` Input exists: ${!!inputLayer}`);
- if (layerColor && layer.path) {
- try {
- console.log(` ๐จ Using processImage for pattern layer ${i} with color ${layerColor}`);
- if (layerColor && layer.path) {
- try {
- await drawFurnitureLayer(ctx, layer.path, {
- tintColor: layerColor,
- zoomState: frozenZoomState,
- highRes: true // โ Enable high-res for patterns
- });
- console.log(` โ Pattern layer ${i} rendered in high resolution`);
- } catch (error) {
- console.error(` โ Failed to render pattern layer ${i}:`, error);
- }
- }
- } catch (error) {
- console.error(` โ Failed to render pattern layer ${i}:`, error);
- }
- } else {
- console.warn(` โ ๏ธ Skipping pattern layer ${i}: missing color or path`);
- }
- }
- console.log("โ Pattern layers step completed");
- // โ NEW STEP 4.5: Add sofa base shadow AFTER patterns
- console.log("4๏ธโฃ.5 Adding sofa base shadow on top of patterns");
- const shadowPath = furniture.baseShadow || furniture.base.replace('.png', '-shadow.png');
- if (shadowPath && furniture.base) {
- console.log(" ๐ Drawing shadow on top of patterns...");
- try {
- await drawFurnitureLayer(ctx, shadowPath, {
- tintColor: null, // No tinting for shadow
- zoomState: frozenZoomState,
- blendMode: "multiply", // Multiply blend for shadow effect
- opacity: 0.7 // Adjust shadow intensity as needed
- });
- console.log(" โ Shadow applied on top of patterns");
- } catch (error) {
- console.log(" โ ๏ธ Shadow file not found, skipping:", shadowPath);
- }
- } else {
- console.log(" โ ๏ธ No shadow path defined, skipping shadow");
- }
- // ===== STEP 5: Draw extras on top =====
- console.log("5๏ธโฃ Drawing extras");
- if (furniture.extras) {
- console.log(" Extras path:", furniture.extras);
- console.log(" Drawing extras without tint (natural colors)");
- try {
- await drawFurnitureLayer(ctx, furniture.extras, {
- tintColor: null,
- zoomState: frozenZoomState,
- opacity: 1.0,
- blendMode: "source-over"
- });
- console.log("โ Extras step completed");
- } catch (error) {
- console.error("โ Failed to draw extras:", error);
- }
- } else {
- console.warn("โ ๏ธ No extras defined in furniture config");
- }
- console.log("๐ =========================");
- console.log("๐ FURNITURE RENDERING COMPLETE (WITH WALL MASK)");
- console.log("๐ =========================");
- // ===== STEP 6: Display result =====
- console.log("6๏ธโฃ Displaying result");
- const dataUrl = canvas.toDataURL("image/png");
- const img = document.createElement("img");
- img.src = dataUrl;
- img.style.cssText = "width: 100%; height: 100%; object-fit: contain;";
- // Clear and append to DOM
- dom.roomMockup.innerHTML = "";
- dom.roomMockup.appendChild(img);
- // Reset all styling including background from fabric mode
- dom.roomMockup.style.cssText = "width: 600px; height: 450px; position: relative; background-image: none; background-color: var(--color-bg-medium);";
- ensureButtonsAfterUpdate();
- console.log("โ Furniture preview displayed in DOM");
- console.log("๐ Final canvas dimensions:", canvas.width, "x", canvas.height);
- console.log("๐ DataURL length:", dataUrl.length);
- } catch (renderError) {
- console.error("โ Error in furniture rendering sequence:", renderError);
- console.error("โ Error stack:", renderError.stack);
- // Fallback: show error message in mockup area
- dom.roomMockup.innerHTML = `
- <div style="
- width: 100%;
- height: 100%;
- display: flex;
- align-items: center;
- justify-content: center;
- background: #f3f4f6;
- color: #dc2626;
- font-family: monospace;
- text-align: center;
- padding: 20px;
- ">
- <div>
- <div style="font-size: 24px; margin-bottom: 10px;">โ ๏ธ</div>
- <div>Furniture Preview Error</div>
- <div style="font-size: 12px; margin-top: 10px;">Check console for details</div>
- </div>
- </div>
- `;
- }
- // โ RESTORE PRESERVED SETTINGS AT THE END
- Object.assign(furnitureViewSettings, preservedSettings);
- console.log("โ Zoom settings restored after rendering:", furnitureViewSettings);
- } catch (mainError) {
- console.error("๐ฅ Critical error in updateFurniturePreview:", mainError);
- console.error("๐ฅ Error stack:", mainError.stack);
- // Ultimate fallback
- if (dom.roomMockup) {
- dom.roomMockup.innerHTML = `
- <div style="
- width: 100%;
- height: 100%;
- display: flex;
- align-items: center;
- justify-content: center;
- background: #fef2f2;
- color: #dc2626;
- font-family: monospace;
- ">
- Critical furniture preview error - check console
- </div>
- `;
- }
- }
- };
- function parseCoordinateFilename(filename) {
- console.log('Before click - Scroll Y:', window.scrollY);
- const parts = filename.split('/');
- const filePart = parts[5]; // "BOMBAY-KITANELLI-VINE.jpg"
- const collectionName = 'coordinates';
- const patternPart = filePart
- .replace(/^BOMBAY-/, '') // Remove "BOMBAY-"
- .replace(/\.jpg$/i, ''); // Remove ".jpg"
- const patternName = patternPart
- .split('-')
- .map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
- .join(' ');
- // No mapping needed to match JSON
- const normalizedPatternName = patternName;
- console.log(`Parsed filename: ${filename} รขโ โ collection: ${collectionName}, pattern: ${normalizedPatternName}`);
- return { collectionName, patternName: normalizedPatternName };
- }
- function loadPatternFromLocalCollections(collectionName, patternName) {
- try {
- if (!appState.collections || !appState.collections.length) {
- console.error("appState.collections is empty or not initialized");
- return null;
- }
- const collection = appState.collections.find(
- c => c.name.toLowerCase() === "coordinates"
- );
- if (!collection) {
- console.error("Coordinates collection not found in appState.collections");
- return null;
- }
- const pattern = collection.patterns.find(
- p => p.name.toLowerCase() === patternName.toLowerCase()
- );
- if (!pattern) {
- console.error(`Pattern ${patternName} not found in coordinates collection`);
- return null;
- }
- console.log(`Loaded pattern: ${pattern.name} from coordinates collection`);
- return { collection, pattern };
- } catch (error) {
- console.error(`Error accessing collections: ${error.message}`);
- return null;
- }
- }
- function setupCoordinateImageHandlers() {
- const coordinateImages = document.querySelectorAll(".coordinate-image");
- console.log(`Found ${coordinateImages.length} coordinate images`);
- coordinateImages.forEach(image => {
- image.removeEventListener("click", handleCoordinateClick);
- image.addEventListener("click", handleCoordinateClick);
- });
- function handleCoordinateClick() {
- const image = this;
- console.log('>>> handleCoordinateClick START <<<');
- // Only store original state if not already stored
- if (!appState.originalPattern) {
- appState.originalPattern = { ...appState.currentPattern };
- appState.originalCoordinates = appState.selectedCollection?.coordinates ? [...appState.selectedCollection.coordinates] : [];
- appState.originalLayerInputs = appState.layerInputs.map((layer, index) => ({
- id: `layer-${index}`,
- label: layer.label,
- inputValue: layer.input.value,
- hex: layer.circle.style.backgroundColor,
- isBackground: layer.isBackground
- }));
- appState.originalCurrentLayers = appState.currentLayers.map(layer => ({ ...layer }));
- console.log("Stored original state:", {
- pattern: appState.originalPattern.name,
- coordinates: appState.originalCoordinates,
- layerInputs: appState.originalLayerInputs,
- currentLayers: appState.originalCurrentLayers
- });
- }
- // Highlight selected image
- document.querySelectorAll(".coordinate-image").forEach(img => img.classList.remove("selected"));
- image.classList.add("selected");
- const filename = image.dataset.filename;
- console.log(`Coordinate image clicked: ${filename}`);
- // Find the coordinate
- const coordinate = appState.selectedCollection?.coordinates?.find(coord => coord.path === filename);
- if (!coordinate) {
- console.error(`Coordinate not found for filename: ${filename}`);
- if (dom.coordinatesContainer) {
- dom.coordinatesContainer.innerHTML += "<p style='color: red;'>Error: Coordinate not found.</p>";
- }
- return;
- }
- console.log(`Found coordinate:`, coordinate);
- // Find the primary pattern layer index (non-background, non-shadow)
- const primaryLayerIndex = appState.currentLayers.findIndex(layer =>
- layer.label !== "Background" &&
- !layer.imageUrl?.toUpperCase().includes("ISSHADOW")
- );
- if (primaryLayerIndex === -1) {
- console.error("No primary pattern layer found in appState.currentLayers:", appState.currentLayers);
- return;
- }
- console.log(`Primary layer index: ${primaryLayerIndex}`);
- // Determine layers to use (handle both layerPath and layerPaths)
- const layerPaths = coordinate.layerPaths || (coordinate.layerPath ? [coordinate.layerPath] : []);
- if (layerPaths.length === 0) {
- console.error(`No layers found for coordinate: ${filename}`);
- return;
- }
- // Load the first coordinate image to get its dimensions
- const coordImage = new Image();
- const normalizedCoordPath = normalizePath(layerPaths[0]);
- console.log(`๐ Coordinate click path: "${layerPaths[0]}" โ normalized: "${normalizedCoordPath}"`);
- coordImage.src = normalizedCoordPath;
- coordImage.onload = () => {
- // Limit coordinate image dimensions to prevent oversized canvases
- const maxDimension = 400;
- const naturalWidth = coordImage.naturalWidth;
- const naturalHeight = coordImage.naturalHeight;
- const scale = Math.min(maxDimension / naturalWidth, maxDimension / naturalHeight, 1);
- const imageWidth = Math.floor(naturalWidth * scale);
- const imageHeight = Math.floor(naturalHeight * scale);
- console.log(`๐ Coordinate image sizing: natural(${naturalWidth}x${naturalHeight}) โ scaled(${imageWidth}x${imageHeight})`);
- // Create layers and labels for all coordinate layers
- const layers = layerPaths.map(path => ({ path }));
- const layerLabels = layerPaths.map((_, index) => index === 0 ? "Flowers" : `Layer ${index + 1}`);
- // Update currentPattern with coordinate data
- appState.currentPattern = {
- ...appState.currentPattern,
- name: coordinate.filename.replace(/\.jpg$/, ''),
- thumbnail: coordinate.path,
- size: [imageWidth / 100, imageHeight / 100], // Convert pixels to inches (assuming 100 DPI)
- layers: layers, // All coordinate layers
- layerLabels: layerLabels,
- tintWhite: false
- };
- console.log(`Updated appState.currentPattern:`, appState.currentPattern);
- // Update the primary pattern layer's imageUrl in currentLayers
- appState.currentLayers = appState.currentLayers.map((layer, index) => {
- if (index === primaryLayerIndex) {
- console.log(`Updating layer at index ${index} with layerPath: ${layerPaths[0]}`);
- return {
- ...layer,
- imageUrl: layerPaths[0] // Update primary layer
- };
- }
- return layer;
- });
- // Preserve the original layer structure and colors
- const currentColors = appState.layerInputs.map(layer => layer.input.value);
- console.log("Preserving colors:", currentColors);
- // Restore layer inputs with preserved colors
- appState.layerInputs = [];
- if (dom.layerInputsContainer) dom.layerInputsContainer.innerHTML = "";
- appState.currentLayers.forEach((layer, index) => {
- const id = `layer-${index}`;
- const isBackground = layer.label === "Background";
- const initialColor = currentColors[index] || (isBackground ? "#FFFFFF" : "Snowbound");
- const layerData = createColorInput(layer.label, id, initialColor, isBackground);
- layerData.input.value = toInitialCaps(initialColor.replace(/^(SW|SC)\d+\s*/i, "").trim());
- layerData.circle.style.backgroundColor = lookupColor(initialColor) || "#FFFFFF";
- // โ ADD THIS LINE - append to DOM
- dom.layerInputsContainer.appendChild(layerData.container);
- appState.layerInputs[index] = layerData;
- console.log(`Set ${layer.label} input to ${layerData.input.value}, circle to ${layerData.circle.style.backgroundColor}, id=${id}`);
- });
- // Update UI
- // updatePreview();
- // const isFurniturePattern = appState.currentPattern?.isFurniture || false;
- updatePreview();
- // Check if we're in fabric mode - if so, only render fabric mockup
- if (appState.isInFabricMode) {
- console.log("๐งต handleCoordinateClick in fabric mode - calling renderFabricMockup()");
- renderFabricMockup();
- } else {
- updateRoomMockup();
- }
- // Add "Back to Pattern" link
- console.log("๐ Adding Back to Pattern button...");
- const coordinatesContainer = document.getElementById("coordinatesContainer");
- console.log("๐ coordinatesContainer found:", !!coordinatesContainer);
- if (coordinatesContainer) {
- let backLink = document.getElementById("backToPatternLink");
- if (backLink) {
- console.log("๐ Removing existing back link");
- backLink.remove();
- }
- backLink = document.createElement("div");
- backLink.id = "backToPatternLink";
- backLink.style.cssText = `
- color: #f0e6d2 !important;
- font-family: 'Island Moments', cursive !important;
- font-size: 1.8rem !important;
- text-align: center !important;
- cursor: pointer !important;
- margin-top: 6rem !important;
- padding: 0.5rem !important;
- transition: color 0.2s !important;
- display: block !important;
- visibility: visible !important;
- opacity: 1 !important;
- z-index: 1000 !important;
- position: relative !important;
- `;
- backLink.textContent = " โ Back to Pattern ";
- backLink.addEventListener("mouseover", () => {
- backLink.style.color = "#beac9f";
- });
- backLink.addEventListener("mouseout", () => {
- backLink.style.color = "#f0e6d2";
- });
- coordinatesContainer.appendChild(backLink);
- backLink.addEventListener("click", restoreOriginalPattern);
- console.log("โ Back to Pattern button added successfully");
- } else {
- console.error("โ coordinatesContainer not found - cannot add back link");
- }
- };
- coordImage.onerror = () => {
- console.error(`Failed to load coordinate image: ${layerPaths[0] || coordinate.layerPath}`);
- };
- }
- }
- function restoreOriginalPattern() {
- try {
- console.log('>>> restoreOriginalPattern START <<<');
- if (!appState.originalPattern || !appState.originalCurrentLayers || !appState.originalLayerInputs) {
- console.warn("No original state to restore", {
- originalPattern: appState.originalPattern,
- originalCurrentLayers: appState.originalCurrentLayers,
- originalLayerInputs: appState.originalLayerInputs
- });
- return;
- }
- console.log("Restoring original pattern:", appState.originalPattern.name,
- "Original state:", {
- layerInputs: appState.originalLayerInputs,
- currentLayers: appState.originalCurrentLayers
- });
- // Restore appState to the original pattern
- appState.currentPattern = { ...appState.originalPattern };
- appState.currentLayers = appState.originalCurrentLayers.map(layer => ({ ...layer }));
- console.log("Restored appState: collection=", appState.selectedCollection.name,
- "pattern=", appState.currentPattern.name);
- // Restore layer inputs
- appState.originalLayerInputs.forEach((layer, index) => {
- const id = layer.id || `layer-${index}`;
- const layerData = createColorInput(layer.label, id, layer.inputValue, layer.isBackground);
- layerData.input.value = toInitialCaps(layer.inputValue.replace(/^(SW|SC)\d+\s*/i, "").trim());
- layerData.circle.style.backgroundColor = layer.hex;
- appState.layerInputs[index] = layerData;
- console.log(`Restored ${layer.label} input to ${layer.inputValue}, circle to ${layer.hex}, id=${id}`);
- });
- console.log("After restore, layerInputs:",
- appState.layerInputs.map(l => ({ id: l.input.id, label: l.label, value: l.input.value })));
- // Update UI
- updatePreview();
- // Check if we're in fabric mode - if so, only render fabric mockup
- if (appState.isInFabricMode) {
- console.log("๐งต restoreOriginalPattern in fabric mode - calling renderFabricMockup()");
- renderFabricMockup();
- } else {
- updateRoomMockup();
- }
- populateCoordinates();
- // Remove Back to Pattern link and clean up
- const coordinatesSection = document.getElementById("coordinatesSection");
- const backLink = document.getElementById("backToPatternLink");
- if (backLink) {
- backLink.remove();
- console.log("Removed Back to Pattern link");
- }
- const errorMessages = coordinatesSection.querySelectorAll("p[style*='color: red']");
- errorMessages.forEach(msg => msg.remove());
- console.log("Cleared error messages:", errorMessages.length);
- console.log('>>> restoreOriginalPattern END <<<');
- } catch (e) {
- console.error("Error restoring original pattern:", e);
- }
- }
- // Update displays with layer compositing
- function updateDisplays() {
- try {
- console.log('updateDisplays called');
- // โ Always update pattern preview
- updatePreview();
- // Check if we're in fabric mode - if so, only render fabric mockup
- if (appState.isInFabricMode) {
- console.log("๐งต updateDisplays in fabric mode - calling renderFabricMockup()");
- renderFabricMockup();
- } else {
- updateRoomMockup();
- }
- populateCoordinates();
- } catch (e) {
- console.error('Error in updateDisplays:', e);
- }
- }
- function handleThumbnailClick(patternId) {
- console.log(`handleThumbnailClick: patternId=${patternId}`);
- if (!patternId) {
- console.error("Invalid pattern ID:", patternId);
- return;
- }
- try {
- // Preserve current mockup
- const originalMockup = appState.selectedCollection?.mockup || "";
- console.log("Preserving mockup for thumbnail click:", originalMockup);
- loadPatternData(appState.selectedCollection, patternId);
- // Update thumbnails
- document.querySelectorAll(".thumbnail").forEach(t => t.classList.remove("selected"));
- const selectedThumb = document.querySelector(`.thumbnail[data-pattern-id="${patternId}"]`);
- if (selectedThumb) {
- selectedThumb.classList.add("selected");
- console.log(`Selected thumbnail: ${patternId}`);
- } else {
- console.warn(`Thumbnail not found for ID: ${patternId}`);
- }
- } catch (error) {
- console.error("Error handling thumbnail click:", error);
- }
- }
- // Generate print preview
- const generatePrintPreview = () => {
- if (!appState.currentPattern) {
- console.error("No current pattern selected for print preview");
- return null;
- }
- const isWall = appState.currentPattern?.isWall || appState.selectedCollection?.name === "wall-panels";
- const backgroundIndex = isWall ? 1 : 0;
- const backgroundInput = appState.layerInputs[backgroundIndex]?.input;
- if (!backgroundInput) {
- console.error(`Background input not found at index ${backgroundIndex}`, appState.layerInputs);
- return null;
- }
- const backgroundColor = lookupColor(backgroundInput.value);
- console.log("Print preview - Background color:", backgroundColor, "isWall:", isWall);
- console.log("Print preview - Layer inputs:", appState.layerInputs.map((li, i) => ({
- index: i,
- value: li?.input?.value
- })));
- const dpi = 100;
- const patternWidthInches = appState.currentPattern?.size?.[0] || 24;
- const patternHeightInches = appState.currentPattern?.size?.[1] || 24;
- const printWidth = Math.round(patternWidthInches * dpi);
- const printHeight = Math.round(patternHeightInches * dpi);
- const aspectRatio = patternHeightInches / patternWidthInches;
- console.log(`Print preview - Pattern: ${patternWidthInches}x${patternHeightInches}, Aspect: ${aspectRatio}`);
- console.log(`Print canvas: ${printWidth}x${printHeight}, DPI: ${dpi}`);
- const printCanvas = document.createElement("canvas");
- const printCtx = printCanvas.getContext("2d", { willReadFrequently: true });
- printCanvas.width = printWidth;
- printCanvas.height = printHeight;
- const collectionName = toInitialCaps(appState.selectedCollection?.name || "Unknown");
- const patternName = toInitialCaps(appState.currentPattern.name || "Pattern");
- let layerLabels = [];
- const processPrintPreview = async () => {
- printCtx.fillStyle = backgroundColor;
- printCtx.fillRect(0, 0, printWidth, printHeight);
- console.log("Print preview - Filled background with:", backgroundColor);
- const isTintWhite = appState.currentPattern?.tintWhite || false;
- console.log(`Print preview - tintWhite flag: ${isTintWhite}`);
- if (isTintWhite && appState.currentPattern?.baseComposite) { } else if (appState.currentPattern?.layers?.length) {
- layerLabels = appState.currentPattern.layers.map((l, i) => ({
- label: appState.currentPattern.layerLabels?.[i] || `Layer ${i + 1}`,
- color: appState.layerInputs[i + (isWall ? 2 : 1)]?.input?.value || "Snowbound"
- }));
- // Add background color to the beginning of the color list
- layerLabels.unshift({
- label: "Background",
- color: backgroundInput.value || "Snowbound"
- });
- const shadowLayers = [];
- const nonShadowLayers = [];
- appState.currentPattern.layers.forEach((layer, index) => {
- const label = layerLabels[index].label;
- const isShadow = layer.isShadow === true;
- (isShadow ? shadowLayers : nonShadowLayers).push({ layer, index, label });
- });
- let nonShadowInputIndex = isWall ? 2 : 1;
- for (const { layer, index, label } of shadowLayers) {
- const layerPath = layer.path || "";
- await new Promise((resolve) => {
- processImage(
- layerPath,
- (processedUrl) => {
- const img = new Image();
- console.log("๐งช processedUrl type:", typeof processedUrl, processedUrl);
- if (processedUrl instanceof HTMLCanvasElement) {
- img.src = processedUrl.toDataURL("image/png");
- } else {
- img.src = processedUrl;
- }
- img.onload = () => {
- printCtx.globalCompositeOperation = "multiply";
- printCtx.globalAlpha = 0.3;
- printCtx.drawImage(img, 0, 0, printWidth, printHeight);
- resolve();
- };
- img.onerror = () => resolve();
- },
- null,
- 2.2,
- true,
- isWall
- );
- });
- }
- for (const { layer, index, label } of nonShadowLayers) {
- const layerPath = layer.path || "";
- const layerInput = appState.layerInputs[nonShadowInputIndex];
- const layerColor = lookupColor(layerInput?.input?.value || "Snowbound");
- await new Promise((resolve) => {
- processImage(
- layerPath,
- (processedUrl) => {
- const img = new Image();
- console.log("๐งช processedUrl type:", typeof processedUrl, processedUrl);
- if (processedUrl instanceof HTMLCanvasElement) {
- img.src = processedUrl.toDataURL("image/png");
- } else {
- img.src = processedUrl;
- }
- img.onload = () => {
- printCtx.globalCompositeOperation = "source-over";
- printCtx.globalAlpha = 1.0;
- printCtx.drawImage(img, 0, 0, printWidth, printHeight);
- nonShadowInputIndex++;
- resolve();
- };
- img.onerror = () => resolve();
- },
- layerColor,
- 2.2,
- false,
- isWall
- );
- });
- }
- }
- const dataUrl = printCanvas.toDataURL("image/png");
- console.log(`Print preview - Generated data URL, length: ${dataUrl.length}`);
- // Generate HTML content
- let textContent = `
- <img src="https://so-animation.com/colorflex/img/SC-header-mage.jpg" alt="SC Logo" class="sc-logo">
- <h2>${collectionName}</h2>
- <h3>${patternName}</h3>
- <ul style="list-style: none; padding: 0;">
- `;
- layerLabels.forEach(({ label, color }, index) => {
- const swNumber = appState.selectedCollection?.curatedColors?.[index] || color || "N/A";
- textContent += `
- <li>${toInitialCaps(label)} | ${swNumber}</li>
- `;
- });
- textContent += "</ul>";
- // Open preview window
- const previewWindow = window.open('', '_blank', 'width=800,height=1200');
- if (!previewWindow) {
- console.error("Print preview - Failed to open preview window");
- return { canvas: printCanvas, dataUrl };
- }
- previewWindow.document.write(`
- <html>
- <head>
- <title>Print Preview</title>
- <link href="https://fonts.googleapis.com/css2?family=Special+Elite&display=swap" rel="stylesheet">
- <style>
- body {
- font-family: 'Special Elite', 'Times New Roman', serif !important;
- padding: 20px;
- margin: 0;
- display: flex;
- justify-content: center;
- align-items: flex-start;
- min-height: 100vh;
- background-color: #111827;
- color: #f0e6d2;
- overflow: auto;
- }
- .print-container {
- text-align: center;
- max-width: 600px;
- width: 100%;
- display: flex;
- flex-direction: column;
- align-items: center;
- background-color: #434341;
- padding: 20px;
- border-radius: 8px;
- }
- .sc-logo {
- width: 400px !important;
- height: auto;
- margin: 0 auto 20px;
- display: block;
- }
- h2 { font-size: 24px; margin: 10px 0; }
- h3 { font-size: 20px; margin: 5px 0; }
- ul { margin: 10px 0; }
- li { margin: 5px 0; font-size: 16px; }
- img { max-width: 100%; height: auto; margin: 20px auto; display: block; }
- .button-container { margin-top: 20px; }
- button {
- font-family: 'Special Elite', serif;
- padding: 10px 20px;
- margin: 0 10px;
- font-size: 16px;
- cursor: pointer;
- background-color: #f0e6d2;
- color: #111827;
- border: none;
- border-radius: 4px;
- }
- button:hover {
- background-color: #e0d6c2;
- }
- </style>
- </head>
- <body>
- <div class="print-container">
- ${textContent}
- <img src="${dataUrl}" alt="Pattern Preview">
- <div class="button-container">
- <button onclick="window.print();">Print</button>
- <button onclick="download()">Download</button>
- <button onclick="window.close();">Close</button>
- </div>
- </div>
- <script>
- function download() {
- const link = document.createElement("a");
- link.href = "${dataUrl}";
- link.download = "${patternName}-print.png";
- link.click();
- }
- </script>
- </body>
- </html>
- `);
- previewWindow.document.close();
- console.log("Print preview - Preview window opened");
- return { canvas: printCanvas, dataUrl, layerLabels, collectionName, patternName };
- };
- return processPrintPreview().catch(error => {
- console.error("Print preview error:", error);
- return null;
- });
- };
- // Start the app
- async function startApp() {
- await initializeApp();
- // Call this when app starts
- await loadFurnitureConfig();
- isAppReady = true;
- console.log("โ App fully initialized and ready.");
- }
- // Run immediately if DOM is already ready
- if (document.readyState === "loading") {
- document.addEventListener("DOMContentLoaded", startApp);
- } else {
- startApp();
- }
- // === PATTERN TYPE HELPERS ===
- function getPatternType(pattern, collection) {
- if (collection?.name === "wall-panels") return "wall-panel";
- if (pattern?.tintWhite) return "tint-white";
- if (collection?.elements?.length) return "element-coloring";
- return "standard";
- }
- function getColorMapping(patternType, currentLayers, layerIndex) {
- switch (patternType) {
- case "wall-panel":
- return currentLayers[layerIndex + 2]; // Skip wall + background
- case "standard":
- const inputLayers = currentLayers.filter(layer => !layer.isShadow);
- return inputLayers[layerIndex + 1]; // Skip background
- case "element-coloring":
- // Future: element-specific color mapping
- const inputLayersElement = currentLayers.filter(layer => !layer.isShadow);
- return inputLayersElement[layerIndex + 1];
- default:
- return currentLayers[layerIndex + 1];
- }
- }
- // Add fabric tuning controls
- function addFabricTuningControls() {
- // Check if controls should be shown
- if (!SHOW_FABRIC_CONTROLS) {
- return; // Exit early if controls are disabled
- }
- // Remove existing controls
- const existingControls = document.getElementById('fabricTuningControls');
- if (existingControls) {
- existingControls.remove();
- }
- // Create control panel
- const controlPanel = document.createElement('div');
- controlPanel.id = 'fabricTuningControls';
- controlPanel.style.cssText = `
- position: fixed;
- top: 20px;
- right: 20px;
- background: rgba(0, 0, 0, 0.9);
- color: white;
- padding: 15px;
- border-radius: 8px;
- border: 2px solid #d4af37;
- z-index: 1000;
- font-family: monospace;
- font-size: 12px;
- max-width: 300px;
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.5);
- `;
- // Add title
- const title = document.createElement('h3');
- title.textContent = '๐งต Fabric Tuning';
- title.style.cssText = 'margin: 0 0 10px 0; color: #d4af37; font-size: 14px;';
- controlPanel.appendChild(title);
- // Create sliders for each parameter
- const params = [
- { key: 'alphaStrength', label: 'Pattern Opacity', min: 0, max: 2, step: 0.1 },
- { key: 'baseTintStrength', label: 'Base Color Tint', min: 0, max: 2, step: 0.1 },
- { key: 'patternContrast', label: 'Pattern Contrast', min: 0.1, max: 3, step: 0.1 },
- { key: 'shadowMultiplier', label: 'Shadow Interaction', min: 0, max: 2, step: 0.1 },
- { key: 'colorVibrance', label: 'Color Vibrance', min: 0, max: 2, step: 0.1 },
- { key: 'glossyStrength', label: 'Glossy Finish', min: 0, max: 2, step: 0.1 }
- ];
- // Add blend mode selector
- const blendModeContainer = document.createElement('div');
- blendModeContainer.style.cssText = 'margin-bottom: 10px;';
- const blendModeLabel = document.createElement('label');
- blendModeLabel.textContent = 'Blend Mode';
- blendModeLabel.style.cssText = 'display: block; margin-bottom: 3px; font-weight: bold;';
- const blendModeSelect = document.createElement('select');
- blendModeSelect.style.cssText = 'width: 100%; padding: 2px; background: #333; color: white; border: 1px solid #555;';
- const blendModes = [
- { value: 'auto', label: 'Auto (Smart)' },
- { value: 'multiply', label: 'Multiply' },
- { value: 'overlay', label: 'Overlay' },
- { value: 'soft-light', label: 'Soft Light' },
- { value: 'hard-light', label: 'Hard Light' },
- { value: 'screen', label: 'Screen' }
- ];
- blendModes.forEach(mode => {
- const option = document.createElement('option');
- option.value = mode.value;
- option.textContent = mode.label;
- if (mode.value === fabricTuning.blendMode) {
- option.selected = true;
- }
- blendModeSelect.appendChild(option);
- });
- blendModeSelect.addEventListener('change', (e) => {
- fabricTuning.blendMode = e.target.value;
- debouncedFabricRender();
- });
- blendModeContainer.appendChild(blendModeLabel);
- blendModeContainer.appendChild(blendModeSelect);
- controlPanel.appendChild(blendModeContainer);
- params.forEach(param => {
- const container = document.createElement('div');
- container.style.cssText = 'margin-bottom: 10px;';
- const label = document.createElement('label');
- label.textContent = param.label;
- label.style.cssText = 'display: block; margin-bottom: 3px; font-weight: bold;';
- const slider = document.createElement('input');
- slider.type = 'range';
- slider.min = param.min;
- slider.max = param.max;
- slider.step = param.step;
- slider.value = fabricTuning[param.key];
- slider.style.cssText = 'width: 100%; margin-bottom: 2px;';
- const valueDisplay = document.createElement('span');
- valueDisplay.textContent = fabricTuning[param.key].toFixed(1);
- valueDisplay.style.cssText = 'color: #d4af37; font-weight: bold;';
- // Update function
- slider.addEventListener('input', (e) => {
- const value = parseFloat(e.target.value);
- fabricTuning[param.key] = value;
- valueDisplay.textContent = value.toFixed(1);
- // Re-render fabric in real-time with debounce
- debouncedFabricRender();
- });
- container.appendChild(label);
- container.appendChild(slider);
- container.appendChild(valueDisplay);
- controlPanel.appendChild(container);
- });
- // Add reset button
- const resetBtn = document.createElement('button');
- resetBtn.textContent = 'Reset to Defaults';
- resetBtn.style.cssText = `
- background: #d4af37;
- color: black;
- border: none;
- padding: 5px 10px;
- border-radius: 4px;
- cursor: pointer;
- font-size: 11px;
- font-weight: bold;
- margin-top: 10px;
- width: 100%;
- `;
- resetBtn.addEventListener('click', () => {
- fabricTuning.alphaStrength = 1.0;
- fabricTuning.baseTintStrength = 1.0;
- fabricTuning.patternContrast = 1.0;
- fabricTuning.shadowMultiplier = 1.0;
- fabricTuning.colorVibrance = 1.2;
- fabricTuning.blendMode = 'auto';
- fabricTuning.glossyStrength = 1.0;
- // Update slider values
- controlPanel.querySelectorAll('input[type="range"]').forEach((slider, index) => {
- slider.value = Object.values(fabricTuning)[index];
- });
- controlPanel.querySelectorAll('span').forEach((span, index) => {
- if (index < 5) { // Only update value displays
- span.textContent = Object.values(fabricTuning)[index].toFixed(1);
- }
- });
- // Update blend mode selector
- const blendModeSelect = controlPanel.querySelector('select');
- if (blendModeSelect) {
- blendModeSelect.value = fabricTuning.blendMode;
- }
- // Re-render with debounce
- debouncedFabricRender();
- });
- controlPanel.appendChild(resetBtn);
- // Add copy values button
- const copyBtn = document.createElement('button');
- copyBtn.textContent = 'Copy Values to Console';
- copyBtn.style.cssText = `
- background: #4a5568;
- color: white;
- border: none;
- padding: 5px 10px;
- border-radius: 4px;
- cursor: pointer;
- font-size: 11px;
- font-weight: bold;
- margin-top: 5px;
- width: 100%;
- `;
- copyBtn.addEventListener('click', () => {
- console.log('๐งต Current fabric tuning values:');
- console.log('fabricTuning = {');
- Object.entries(fabricTuning).forEach(([key, value]) => {
- console.log(` ${key}: ${value},`);
- });
- console.log('};');
- });
- controlPanel.appendChild(copyBtn);
- // Add to document
- document.body.appendChild(controlPanel);
- }
- // Function to remove fabric tuning controls
- function removeFabricTuningControls() {
- const existingControls = document.getElementById('fabricTuningControls');
- if (existingControls) {
- existingControls.remove();
- }
- }
- // Simple fabric mockup function
- async function renderFabricMockup() {
- console.log("๐งต ================================");
- console.log("๐งต FABRIC MOCKUP STARTING");
- console.log("๐งต ================================");
- const canvas = document.createElement("canvas");
- const ctx = canvas.getContext("2d");
- // Will be dynamically sized based on first loaded image
- let canvasWidth = 600; // Default fallback
- let canvasHeight = 450; // Default fallback
- // Get fabric config with error handling
- console.log("๐ Global furnitureConfig:", furnitureConfig);
- console.log("๐ Collection furnitureConfig:", appState.selectedCollection?.furnitureConfig);
- // Try to get furniture config from collection first, then fall back to global
- let actualFurnitureConfig = appState.selectedCollection?.furnitureConfig || furnitureConfig;
- console.log("๐ Using furnitureConfig:", actualFurnitureConfig);
- const fabricConfig = actualFurnitureConfig?.fabric;
- if (!fabricConfig) {
- console.error("โ Fabric config not found in furnitureConfig!");
- console.log("๐ Available furniture config keys:", Object.keys(actualFurnitureConfig || {}));
- return;
- }
- console.log("๐ Fabric config:", fabricConfig);
- // Get background color (first layer is Background)
- console.log("๐ Current layers:", appState.currentLayers);
- console.log("๐ First layer:", appState.currentLayers[0]);
- const backgroundColor = lookupColor(appState.currentLayers[0]?.color || "Snowbound");
- console.log("๐จ Background color:", backgroundColor);
- console.log("๐ Base tint strength:", fabricTuning.baseTintStrength);
- try {
- // 1. Load and draw room mockup background
- const mockupBg = new Image();
- mockupBg.crossOrigin = "anonymous";
- await new Promise((resolve, reject) => {
- mockupBg.onload = resolve;
- mockupBg.onerror = reject;
- mockupBg.src = `https://so-animation.com/colorflex/${fabricConfig.mockup}`;
- });
- // Set canvas size based on mockup image dimensions
- canvasWidth = mockupBg.width;
- canvasHeight = mockupBg.height;
- canvas.width = canvasWidth;
- canvas.height = canvasHeight;
- console.log(`๐ Canvas sized to match mockup: ${canvasWidth}x${canvasHeight}`);
- // Draw room background at full resolution
- ctx.drawImage(mockupBg, 0, 0);
- // 2. Load fabric base for later use
- const fabricBase = new Image();
- fabricBase.crossOrigin = "anonymous";
- await new Promise((resolve, reject) => {
- fabricBase.onload = resolve;
- fabricBase.onerror = reject;
- fabricBase.src = `https://so-animation.com/colorflex/${fabricConfig.base}`;
- });
- console.log(`๐ Fabric base: ${fabricBase.width}x${fabricBase.height}`);
- // 3. Create tinted base layer using fabric base alpha channel
- const baseCanvas = document.createElement("canvas");
- const baseCtx = baseCanvas.getContext("2d");
- baseCanvas.width = canvasWidth;
- baseCanvas.height = canvasHeight;
- // Draw fabric base to get alpha channel at full resolution
- baseCtx.drawImage(fabricBase, 0, 0, canvasWidth, canvasHeight);
- // Extract alpha channel and apply background color tint
- const baseImageData = baseCtx.getImageData(0, 0, canvasWidth, canvasHeight);
- const baseData = baseImageData.data;
- // Parse background color
- const bgColorMatch = backgroundColor.match(/^#([0-9a-f]{6})$/i);
- if (bgColorMatch) {
- const bgR = parseInt(bgColorMatch[1].substr(0, 2), 16);
- const bgG = parseInt(bgColorMatch[1].substr(2, 2), 16);
- const bgB = parseInt(bgColorMatch[1].substr(4, 2), 16);
- for (let j = 0; j < baseData.length; j += 4) {
- const r = baseData[j];
- const g = baseData[j + 1];
- const b = baseData[j + 2];
- const alpha = baseData[j + 3];
- if (alpha > 0) {
- const tintStrength = fabricTuning.baseTintStrength;
- // Apply background color tint
- baseData[j] = Math.floor(bgR * tintStrength + r * (1 - tintStrength));
- baseData[j + 1] = Math.floor(bgG * tintStrength + g * (1 - tintStrength));
- baseData[j + 2] = Math.floor(bgB * tintStrength + b * (1 - tintStrength));
- // Keep original alpha channel
- }
- }
- baseCtx.putImageData(baseImageData, 0, 0);
- }
- console.log("โ Created tinted base layer with fabric alpha channel");
- // Load pattern layers using the fabric config from furniture-config.json
- const patternSlug = createPatternSlug(appState.currentPattern.name);
- const pattern = appState.currentPattern;
- console.log(`๐ Pattern layers available:`, pattern.layers);
- console.log(`๐ Fabric config patternPathTemplate:`, fabricConfig.patternPathTemplate);
- // Process pattern layers (skip Background layer at index 0)
- for (let i = 0; i < pattern.layers.length; i++) {
- const layer = pattern.layers[i];
- console.log(`๐ Pattern layer ${i} object:`, layer);
- // Extract filename from layer's path or imageUrl and change extension to .png
- let layerFileName;
- if (typeof layer === 'string') {
- layerFileName = layer;
- } else if (layer.path) {
- const originalFileName = layer.path.split('/').pop();
- layerFileName = originalFileName.replace(/\.(jpg|jpeg)$/i, '.png');
- } else if (layer.imageUrl) {
- const originalFileName = layer.imageUrl.split('/').pop();
- layerFileName = originalFileName.replace(/\.(jpg|jpeg)$/i, '.png');
- } else {
- layerFileName = `${patternSlug}_layer-${i+1}.png`;
- }
- // Use the patternPathTemplate from fabric config
- const layerPath = `https://so-animation.com/colorflex/${fabricConfig.patternPathTemplate
- .replace('{collection}', appState.selectedCollection.name)
- .replace('{patternSlug}', patternSlug)}${layerFileName}`;
- console.log(`๐ Loading pattern layer ${i}: ${layerPath}`);
- try {
- const layerImg = new Image();
- layerImg.crossOrigin = "anonymous";
- await new Promise((resolve, reject) => {
- layerImg.onload = resolve;
- layerImg.onerror = reject;
- layerImg.src = layerPath;
- });
- // Apply pattern to pattern composite (like pattern preview)
- const tempCanvas = document.createElement("canvas");
- const tempCtx = tempCanvas.getContext("2d");
- tempCanvas.width = canvasWidth;
- tempCanvas.height = canvasHeight;
- // Draw the pattern image at full resolution
- tempCtx.drawImage(layerImg, 0, 0, canvasWidth, canvasHeight);
- // Get the layer's color from appState (pattern layers start at index 1 after Background)
- const colorIndex = i + 1; // Skip Background layer at index 0
- const layerColor = lookupColor(appState.currentLayers[colorIndex]?.color || "#FFFFFF");
- console.log(`๐จ Using color ${layerColor} for pattern layer ${i} (color index ${colorIndex})`);
- // Parse pattern color (hex to RGB)
- const colorMatch = layerColor.match(/^#([0-9a-f]{6})$/i);
- if (!colorMatch) {
- console.warn(`โ ๏ธ Invalid color format for layer ${i}: ${layerColor}`);
- continue;
- }
- const colorR = parseInt(colorMatch[1].substr(0, 2), 16);
- const colorG = parseInt(colorMatch[1].substr(2, 2), 16);
- const colorB = parseInt(colorMatch[1].substr(4, 2), 16);
- // Apply color vibrance adjustment
- const vibrance = fabricTuning.colorVibrance;
- const vibranceR = Math.floor(127 + (colorR - 127) * vibrance);
- const vibranceG = Math.floor(127 + (colorG - 127) * vibrance);
- const vibranceB = Math.floor(127 + (colorB - 127) * vibrance);
- console.log(`๐จ Pattern layer ${i} RGB: ${vibranceR}, ${vibranceG}, ${vibranceB}`);
- // Extract pattern luminance and apply color (like pattern preview)
- const imageData = tempCtx.getImageData(0, 0, canvasWidth, canvasHeight);
- const data = imageData.data;
- let nonTransparentPixels = 0;
- let averageLuminance = 0;
- // Apply pattern processing (similar to pattern preview)
- for (let j = 0; j < data.length; j += 4) {
- const r = data[j];
- const g = data[j + 1];
- const b = data[j + 2];
- const alpha = data[j + 3];
- if (alpha > 0) {
- nonTransparentPixels++;
- // Calculate pattern luminance
- let patternLuminance = 0.299 * r + 0.587 * g + 0.114 * b;
- // Apply pattern contrast adjustment
- patternLuminance = Math.pow(patternLuminance / 255, 1 / fabricTuning.patternContrast) * 255;
- averageLuminance += patternLuminance;
- // Create colored pattern with luminance-based opacity
- const opacity = (patternLuminance / 255) * fabricTuning.alphaStrength;
- data[j] = vibranceR;
- data[j + 1] = vibranceG;
- data[j + 2] = vibranceB;
- data[j + 3] = Math.min(255, opacity * 255);
- } else {
- data[j + 3] = 0;
- }
- }
- if (nonTransparentPixels > 0) {
- averageLuminance /= nonTransparentPixels;
- console.log(`๐ Pattern layer ${i}: ${nonTransparentPixels} pixels, avg luminance: ${averageLuminance.toFixed(2)}`);
- } else {
- console.warn(`โ ๏ธ Pattern layer ${i}: No non-transparent pixels found`);
- }
- // Put the processed pattern back
- tempCtx.putImageData(imageData, 0, 0);
- // Apply to base canvas using normal blending
- baseCtx.globalCompositeOperation = "source-over";
- baseCtx.drawImage(tempCanvas, 0, 0);
- console.log(`๐ Applied pattern layer ${i} to base canvas`);
- console.log(`โ Pattern layer ${i} (${layerFileName}) applied`);
- } catch (error) {
- console.warn(`โ ๏ธ Pattern layer ${i} (${layerFileName}) failed:`, error);
- }
- }
- // 4. Final compositing in correct order
- console.log("๐งต Final compositing: mockup -> base -> patterns -> fabric shadows");
- // Layer 1: Mockup (unaltered room background)
- ctx.drawImage(mockupBg, 0, 0);
- // Layer 2: Base + Patterns (composited with alpha channel)
- ctx.globalCompositeOperation = "source-over";
- ctx.drawImage(baseCanvas, 0, 0);
- // Layer 3: Fabric base for shadows (multiply to bring shadows back)
- ctx.globalCompositeOperation = "multiply";
- ctx.drawImage(fabricBase, 0, 0, canvasWidth, canvasHeight);
- // Layer 4: Glossy finish (screen blend for shine effect)
- if (fabricTuning.glossyStrength > 0) {
- try {
- const fabricGlossy = new Image();
- fabricGlossy.crossOrigin = "anonymous";
- await new Promise((resolve, reject) => {
- fabricGlossy.onload = resolve;
- fabricGlossy.onerror = reject;
- // Use fabric-glossy.png from the same directory as fabric-base.png
- const glossyPath = fabricConfig.base.replace('fabric-base.png', 'fabric-glossy.png');
- fabricGlossy.src = `https://so-animation.com/colorflex/${glossyPath}`;
- });
- console.log(`๐ Fabric glossy: ${fabricGlossy.width}x${fabricGlossy.height}`);
- // Apply glossy layer with screen blend mode and tunable opacity
- ctx.globalCompositeOperation = "screen";
- ctx.globalAlpha = fabricTuning.glossyStrength;
- ctx.drawImage(fabricGlossy, 0, 0, canvasWidth, canvasHeight);
- // Reset alpha and composite operation
- ctx.globalAlpha = 1.0;
- ctx.globalCompositeOperation = "source-over";
- console.log("โ Glossy layer applied with screen blend");
- } catch (error) {
- console.warn("โ ๏ธ Glossy layer failed to load:", error);
- // Continue without glossy layer if it fails
- }
- }
- // Reset composite operation
- ctx.globalCompositeOperation = "source-over";
- console.log("โ All layers composited in correct order");
- // Update display - try both possible element references
- let roomMockup = document.getElementById('roomMockup');
- if (!roomMockup && dom?.roomMockup) {
- roomMockup = dom.roomMockup;
- }
- console.log("๐ roomMockup element found:", !!roomMockup);
- console.log("๐ dom.roomMockup available:", !!dom?.roomMockup);
- if (roomMockup) {
- const dataURL = canvas.toDataURL();
- console.log("๐ Canvas dataURL length:", dataURL.length);
- console.log("๐ roomMockup element type:", roomMockup.tagName);
- // Check if it's an img or div element
- if (roomMockup.tagName === 'IMG') {
- roomMockup.src = dataURL;
- console.log("โ Set fabric mockup as img src");
- } else {
- // It's a div - preserve back button but clear other content
- console.log("๐ Div innerHTML before:", roomMockup.innerHTML.substring(0, 100));
- // Save existing back button if it exists
- const existingButton = roomMockup.querySelector('#backToPatternsBtn');
- // Clear the div content
- roomMockup.innerHTML = '';
- // Clear the CSS background color to make background image visible
- roomMockup.style.backgroundColor = 'transparent';
- // Set background image
- roomMockup.style.backgroundImage = `url(${dataURL})`;
- roomMockup.style.backgroundSize = 'contain';
- roomMockup.style.backgroundRepeat = 'no-repeat';
- roomMockup.style.backgroundPosition = 'center';
- // Restore the back button if it existed
- if (existingButton) {
- roomMockup.appendChild(existingButton);
- console.log("โ Restored back button after clearing div");
- }
- console.log("โ Set fabric mockup as div background and cleared other content");
- }
- console.log("โ Fabric mockup displayed to element:", roomMockup.id);
- } else {
- console.error("โ No roomMockup element found!");
- }
- // Add back button for fabric mode (but only if not already present)
- if (!document.getElementById('backToPatternsBtn')) {
- addBackToPatternsButton();
- }
- // Add fabric tuning controls
- addFabricTuningControls();
- } catch (error) {
- console.error("โ Fabric mockup error:", error);
- }
- }
- // Add Try Fabric button functionality
- function addTryFabricButton() {
- console.log("๐งต addTryFabricButton called");
- console.log("๐งต selectedCollection:", appState.selectedCollection?.name);
- // Check if we're in a compatible collection for fabric
- if (!appState.selectedCollection || appState.selectedCollection.name !== "botanicals") {
- console.log("๐งต Not botanicals collection, skipping fabric button");
- return;
- }
- console.log("๐งต Creating Try Fabric button");
- const existingButton = document.getElementById('tryFabricBtn');
- if (existingButton) {
- existingButton.remove();
- }
- const button = document.createElement('button');
- button.id = 'tryFabricBtn';
- button.textContent = 'Try Fabric';
- button.className = 'btn btn-primary';
- button.style.cssText = `
- margin-top: 10px;
- padding: 8px 16px;
- background-color: #007bff;
- color: white;
- border: none;
- border-radius: 4px;
- cursor: pointer;
- `;
- button.addEventListener('click', () => {
- console.log("๐งต ================================");
- console.log("๐งต TRY FABRIC BUTTON CLICKED");
- console.log("๐งต ================================");
- renderFabricMockup();
- });
- // Add button to the appropriate location
- const tryFurnitureBtn = document.getElementById('tryFurnitureBtn');
- if (tryFurnitureBtn) {
- tryFurnitureBtn.parentNode.insertBefore(button, tryFurnitureBtn.nextSibling);
- } else {
- const controlsContainer = document.querySelector('.controls-container') || document.body;
- controlsContainer.appendChild(button);
- }
- }
- // Add this line at the bottom of your CFM.js file to expose the function globally:
- window.addTryFurnitureButton = addTryFurnitureButton;
- window.getCompatibleFurniture = getCompatibleFurniture;
- window.showFurnitureModal = showFurnitureModal;
- window.selectFurniture = selectFurniture;
- window.renderFabricMockup = renderFabricMockup;
- window.addTryFabricButton = addTryFabricButton;
- // Debug function to manually test fabric
- window.testFabric = function() {
- console.log("๐งต Manual fabric test called");
- renderFabricMockup();
- };
- // Simple red canvas test
- window.testRedCanvas = function() {
- console.log("๐ด Testing red canvas display");
- const canvas = document.createElement("canvas");
- const ctx = canvas.getContext("2d");
- canvas.width = 600;
- canvas.height = 450;
- ctx.fillStyle = "red";
- ctx.fillRect(0, 0, 600, 450);
- ctx.fillStyle = "white";
- ctx.font = "48px Arial";
- ctx.fillText("FABRIC TEST", 150, 250);
- const roomMockup = document.getElementById('roomMockup') || dom?.roomMockup;
- if (roomMockup) {
- roomMockup.src = canvas.toDataURL();
- console.log("๐ด Red canvas set to roomMockup");
- } else {
- console.error("โ No roomMockup element found");
- }
- };
- // Simple fabric function that just fits a 3840x2160 image into 600x450
- window.simpleFabricTest = function() {
- console.log("๐งต SIMPLE FABRIC TEST");
- const canvas = document.createElement("canvas");
- const ctx = canvas.getContext("2d");
- canvas.width = 600;
- canvas.height = 450;
- // Fill with a color first
- ctx.fillStyle = "#F0F0E9";
- ctx.fillRect(0, 0, 600, 450);
- const img = new Image();
- img.crossOrigin = "anonymous";
- img.onload = function() {
- console.log(`Image loaded: ${img.width}x${img.height}`);
- // Calculate scale to fit 3840x2160 into 600x450
- const scaleX = 600 / img.width;
- const scaleY = 450 / img.height;
- const scale = Math.min(scaleX, scaleY);
- console.log(`Scale: ${scale} (${scaleX}, ${scaleY})`);
- const w = img.width * scale;
- const h = img.height * scale;
- const x = (600 - w) / 2;
- const y = (450 - h) / 2;
- console.log(`Drawing at: ${x}, ${y}, ${w}x${h}`);
- ctx.drawImage(img, x, y, w, h);
- // Update display
- const roomMockup = document.getElementById('roomMockup');
- if (roomMockup) {
- roomMockup.src = canvas.toDataURL();
- console.log("โ Simple fabric test complete");
- }
- };
- img.src = "https://so-animation.com/colorflex/data/fabric/fabric-base.png";
- };
- window.addBackToPatternsButton = addBackToPatternsButton;
- window.initializeTryFurnitureFeature = initializeTryFurnitureFeature;
Advertisement
Add Comment
Please, Sign In to add comment