@@ -300,6 +300,24 @@ body {
|
||||
z-index: 4;
|
||||
}
|
||||
|
||||
.layer-foreground {
|
||||
z-index: 5;
|
||||
pointer-events: none; /* Allow clicks to pass through to controls below */
|
||||
}
|
||||
|
||||
/* Layer transform modifiers */
|
||||
.layer-flip-horizontal {
|
||||
transform: scaleX(-1);
|
||||
}
|
||||
|
||||
.layer-flip-vertical {
|
||||
transform: scaleY(-1);
|
||||
}
|
||||
|
||||
.layer-flip-both {
|
||||
transform: scale(-1, -1);
|
||||
}
|
||||
|
||||
.config-preview-notice {
|
||||
position: absolute;
|
||||
bottom: 10px;
|
||||
|
||||
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 3.6 KiB |
|
After Width: | Height: | Size: 2.3 KiB |
@@ -106,7 +106,13 @@ let history = ['start'];
|
||||
function initFromURL() {
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
|
||||
// Priority 1: Direct product view
|
||||
// Priority 0: Direct product from Flask route (e.g., /quiz/product/404)
|
||||
if (window.INITIAL_PRODUCT && window.INITIAL_PRODUCT !== '') {
|
||||
showProductByCode(window.INITIAL_PRODUCT);
|
||||
return;
|
||||
}
|
||||
|
||||
// Priority 1: Direct product view from query param
|
||||
const productCode = params.get('p');
|
||||
if (productCode) {
|
||||
accumulatedBitValue = parseInt(params.get('b') || '0', 10);
|
||||
@@ -705,6 +711,7 @@ function showProductDetail(product) {
|
||||
|
||||
// Get product image configuration
|
||||
const productImage = product.image || `https://via.placeholder.com/400x400/e0e0e0/666?text=${encodeURIComponent(prodCode)}`;
|
||||
const useCanvasAPI = product.imageConfig && product.imageConfig.useCanvasAPI === true;
|
||||
const hasLayeredImages = product.imageConfig && product.imageConfig.layered === true;
|
||||
const hasFlatImages = product.flatImages === true || product.imagePattern;
|
||||
const useDynamicAPI = product.useDynamicAPI === true;
|
||||
@@ -795,20 +802,21 @@ function showProductDetail(product) {
|
||||
|
||||
const contentDiv = document.getElementById('content');
|
||||
|
||||
// Build the image display (layered, flat, or dynamic API)
|
||||
// Build the image display (Canvas API, layered, flat, or dynamic API)
|
||||
let imageDisplayHtml = '';
|
||||
|
||||
if (hasLayeredImages && !hasFlatImages && !useDynamicAPI) {
|
||||
// Use layered image system
|
||||
const basePath = product.imageConfig.basePath || '';
|
||||
const layers = product.imageConfig.layers || {};
|
||||
if (useCanvasAPI || (hasLayeredImages && !hasFlatImages && !useDynamicAPI)) {
|
||||
// Use layered image system (Canvas API or static paths)
|
||||
const basePath = product.imageConfig?.basePath || '';
|
||||
const layers = product.imageConfig?.layers || {};
|
||||
|
||||
imageDisplayHtml = `
|
||||
<div class="door-configurator" id="door-configurator">
|
||||
${layers.base ? `<img src="${basePath}${layers.base}" class="layer-base" alt="Base">` : ''}
|
||||
<img src="" class="layer-base" alt="Base">
|
||||
<img src="" class="layer-door" id="door-layer" alt="Door" style="display:none;">
|
||||
${layers.hardware ? `<img src="${basePath}${layers.hardware}" class="layer-hardware" id="hardware-layer" alt="Hardware">` : ''}
|
||||
<img src="" class="layer-hardware" id="hardware-layer" alt="Hardware" style="display:none;">
|
||||
<img src="" class="layer-overlay" id="overlay-layer" alt="View" style="display:none;">
|
||||
<img src="" class="layer-foreground" alt="Foreground" style="display:none;">
|
||||
<div class="config-preview-notice" id="config-notice" style="display:none;">
|
||||
⚠️ Preview not available for this configuration
|
||||
</div>
|
||||
@@ -958,7 +966,11 @@ function showProductDetail(product) {
|
||||
updateBreadcrumb();
|
||||
|
||||
// Initialize images based on type
|
||||
if (hasLayeredImages && !hasFlatImages && !useDynamicAPI) {
|
||||
if (useCanvasAPI) {
|
||||
// Use Canvas API with hierarchical fallback
|
||||
updateCanvasAPIPreview(prodCode);
|
||||
} else if (hasLayeredImages && !hasFlatImages && !useDynamicAPI) {
|
||||
// Use static layered images
|
||||
updateProductPreview(prodCode);
|
||||
} else if (hasFlatImages || useDynamicAPI) {
|
||||
// Flat images or dynamic API - will update on dropdown change
|
||||
@@ -969,18 +981,138 @@ function showProductDetail(product) {
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Canvas API Functions - Hierarchical Image Fallback System
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Build Canvas API URL for a specific layer
|
||||
* @param {string} productCode - Product code
|
||||
* @param {string} layer - Layer name (base, door, hardware, overlay, foreground)
|
||||
* @param {string} color - Optional color
|
||||
* @param {string} material - Optional material
|
||||
* @returns {string} Canvas API URL
|
||||
*/
|
||||
function buildCanvasAPIUrl(productCode, layer, color = null, material = null) {
|
||||
let url = `${BASE_URL}/api/canvas/${productCode}/${layer}`;
|
||||
const params = new URLSearchParams();
|
||||
|
||||
if (color) params.append('color', color.toLowerCase());
|
||||
if (material) params.append('material', material.toLowerCase());
|
||||
|
||||
const queryString = params.toString();
|
||||
return queryString ? `${url}?${queryString}` : url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update layered image preview using Canvas API
|
||||
* @param {string} productCode - Product code
|
||||
*/
|
||||
function updateCanvasAPIPreview(productCode) {
|
||||
const product = productData.find(p => p.productCode === productCode || p.id === productCode);
|
||||
if (!product) return;
|
||||
|
||||
// Get selected values
|
||||
const selectedColor = document.getElementById('config-color')?.value || '';
|
||||
const selectedMaterial = document.getElementById('config-material')?.value || '';
|
||||
|
||||
// Get layer transform configuration (for flipping, etc.)
|
||||
const layerTransforms = product.imageConfig?.layerTransforms || {};
|
||||
|
||||
// Helper function to apply transform classes
|
||||
function applyTransform(element, layerName) {
|
||||
if (!element) return;
|
||||
// Remove any existing transform classes
|
||||
element.classList.remove('layer-flip-horizontal', 'layer-flip-vertical', 'layer-flip-both');
|
||||
// Apply new transform if configured
|
||||
const transform = layerTransforms[layerName];
|
||||
if (transform) {
|
||||
element.classList.add(`layer-${transform}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Update each layer
|
||||
const baseLayer = document.querySelector('.layer-base');
|
||||
const doorLayer = document.getElementById('door-layer');
|
||||
const hardwareLayer = document.getElementById('hardware-layer');
|
||||
const overlayLayer = document.getElementById('overlay-layer');
|
||||
const foregroundLayer = document.querySelector('.layer-foreground');
|
||||
|
||||
// Base layer (no color variation)
|
||||
if (baseLayer && baseLayer.tagName === 'IMG') {
|
||||
baseLayer.src = buildCanvasAPIUrl(productCode, 'base');
|
||||
applyTransform(baseLayer, 'base');
|
||||
baseLayer.onerror = function() {
|
||||
console.warn('Base layer not found via Canvas API');
|
||||
};
|
||||
}
|
||||
|
||||
// Door layer (with color)
|
||||
if (doorLayer) {
|
||||
applyTransform(doorLayer, 'door');
|
||||
if (selectedColor) {
|
||||
doorLayer.src = buildCanvasAPIUrl(productCode, 'door', selectedColor);
|
||||
doorLayer.style.display = 'block';
|
||||
doorLayer.onerror = function() {
|
||||
console.warn(`Door layer not found for color: ${selectedColor}`);
|
||||
this.style.display = 'none';
|
||||
showConfigNotice(true);
|
||||
};
|
||||
doorLayer.onload = function() {
|
||||
showConfigNotice(false);
|
||||
};
|
||||
} else {
|
||||
doorLayer.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
// Hardware layer
|
||||
if (hardwareLayer) {
|
||||
hardwareLayer.src = buildCanvasAPIUrl(productCode, 'hardware');
|
||||
applyTransform(hardwareLayer, 'hardware');
|
||||
hardwareLayer.onerror = function() {
|
||||
console.warn('Hardware layer not found via Canvas API');
|
||||
this.style.display = 'none';
|
||||
};
|
||||
hardwareLayer.onload = function() {
|
||||
this.style.display = 'block';
|
||||
};
|
||||
}
|
||||
|
||||
// Foreground layer
|
||||
if (foregroundLayer && foregroundLayer.tagName === 'IMG') {
|
||||
foregroundLayer.src = buildCanvasAPIUrl(productCode, 'foreground');
|
||||
applyTransform(foregroundLayer, 'foreground');
|
||||
foregroundLayer.onerror = function() {
|
||||
console.warn('Foreground layer not found via Canvas API');
|
||||
// This is fine - foreground is optional
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Update product preview based on configuration
|
||||
// ============================================================================
|
||||
|
||||
function updateProductPreview(productCode) {
|
||||
console.log('updateProductPreview called with:', productCode);
|
||||
|
||||
const product = productData.find(p => p.productCode === productCode || p.id === productCode);
|
||||
if (!product) return;
|
||||
|
||||
// Check which system to use
|
||||
// Check which system to use - Canvas API has priority
|
||||
const useCanvasAPI = product.imageConfig && product.imageConfig.useCanvasAPI === true;
|
||||
const hasFlatImages = product.flatImages === true || product.imagePattern;
|
||||
const useDynamicAPI = product.useDynamicAPI === true;
|
||||
|
||||
console.log('Product flags - hasFlatImages:', hasFlatImages, 'useDynamicAPI:', useDynamicAPI);
|
||||
console.log('Product flags - useCanvasAPI:', useCanvasAPI, 'hasFlatImages:', hasFlatImages, 'useDynamicAPI:', useDynamicAPI);
|
||||
|
||||
// Priority: Canvas API > Flat Images > Dynamic API > Layered Static
|
||||
if (useCanvasAPI) {
|
||||
// Use Canvas API with hierarchical fallback
|
||||
updateCanvasAPIPreview(productCode);
|
||||
return;
|
||||
}
|
||||
|
||||
if (hasFlatImages) {
|
||||
// Use pre-rendered flat images
|
||||
@@ -1810,6 +1942,7 @@ startOver = function() {
|
||||
|
||||
// Initialize search bar when page loads
|
||||
window.addEventListener('DOMContentLoaded', function() {
|
||||
init(); // Load quiz data and initialize
|
||||
initSearchBar();
|
||||
toggleSearchBar();
|
||||
});
|
||||
|
||||