← back to Dw Theme Boost Fix

templates/page.spec-sheet.liquid

774 lines

{% comment %}
  Simple Spec Sheet - Refined version
  - Larger image (500x500px)
  - Single column if space available
  - Title case, not bold
{% endcomment %}

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Spec Sheet - Designer Wallcoverings</title>

    <!-- jsPDF Library -->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js"></script>

    <style>
        * { margin: 0; padding: 0; box-sizing: border-box; }
        body { font-family: Arial, Helvetica, sans-serif; background: #f5f5f5; padding: 20px; }
        .spec-page { width: 8.5in; min-height: 11in; margin: 0 auto; background: white; box-shadow: 0 4px 20px rgba(0,0,0,0.1); display: flex; flex-direction: column; position: relative; }

        .header { padding: 30px 40px; text-align: center; border-bottom: 2px solid #2c5f7f; }
        .logo { max-width: 200px; margin: 0 auto; display: block; }
        .download-btn { position: absolute; top: 30px; right: 30px; background: #c8a551; color: white; padding: 10px 20px; border: none; border-radius: 20px; cursor: pointer; font-size: 12px; font-weight: bold; box-shadow: 0 2px 8px rgba(0,0,0,0.2); }
        .download-btn:hover { background: #b8954a; }

        .printable-content { flex: 1; padding: 30px 40px; }

        /* Title - single line, title case, not bold, centered */
        .product-title {
            font-size: 18px;
            color: #333;
            margin-bottom: 15px;
            line-height: 1.1;
            font-weight: normal;
            text-align: center;
            white-space: nowrap;
            overflow: hidden;
            text-overflow: ellipsis;
            text-transform: capitalize;
        }

        /* Larger centered image */
        .product-img {
            max-width: 500px;
            max-height: 500px;
            object-fit: contain;
            display: block;
            margin: 0 0 20px 0;
            width: 100%;
        }

        /* Single column by default, 2 columns only if lots of data */
        .spec-grid {
            display: grid;
            grid-template-columns: 1fr;
            gap: 12px;
        }

        /* Switch to 2 columns if more than 15 items */
        .spec-grid.many-items {
            grid-template-columns: repeat(2, 1fr);
            gap: 12px 20px;
        }

        /* Content wrapper to align with image */
        .content-wrapper {
            max-width: 500px;
            margin: 0 auto;
            display: flex;
            flex-direction: column;
            align-items: flex-start;
        }

        .spec-item {
            font-size: 14px;
            line-height: 1.6;
            display: flex;
            align-items: baseline;
            text-align: left;
        }
        .spec-item strong {
            color: #333;
            font-weight: bold;
            margin-right: 6px;
            white-space: nowrap;
        }
        .spec-item span {
            color: #555;
            flex: 1;
            text-align: left;
        }

        .footer { padding: 20px 40px; text-align: center; border-top: 2px solid #2c5f7f; background: #f9f9f9; margin-top: auto; }
        .footer-line { font-size: 10px; color: #666; margin: 3px 0; }
        .footer-line.company { font-size: 12px; font-weight: bold; color: #333; margin-bottom: 6px; }

        #pdf-content { background: white; min-height: 11in; display: flex; flex-direction: column; }
        .loading { text-align: center; padding: 60px 40px; }
        .loading h1 { color: #c8a551; margin-bottom: 20px; }

        @media print {
            .download-btn { display: none !important; }
            .spec-page { box-shadow: none; }
        }
    </style>
</head>
<body>
    <div class="spec-page">
        <div class="header">
            <img src="https://www.designerwallcoverings.com/cdn/shop/files/Screen_Shot_2019-02-06_at_12.08.24_PM_300x172.png?v=1613539653" alt="Designer Wallcoverings" class="logo">
            <button class="download-btn" onclick="downloadPDF()" id="pdfBtn" style="display:none;">📄 Download PDF</button>
        </div>

        <div id="pdf-content">
            <div class="printable-content" id="content">
                <div class="loading">
                    <h1>Loading Product...</h1>
                    <p>Please wait...</p>
                </div>
            </div>

            <div class="footer">
                <div class="footer-line company">Designer Wallcoverings Purchasing Agency</div>
                <div class="footer-line">15442 Ventura Bl #102, Sherman Oaks, CA 91403</div>
                <div class="footer-line">Phone: (888) 373-4564 | Email: info@designerwallcoverings.com | www.designerwallcoverings.com</div>
                <div class="footer-line" style="margin-top: 10px; font-style: italic;">Purchasing Agency for the Wallcovering and Surface Material Industry</div>
                <div class="footer-line" style="margin-top: 8px; font-size: 9px;">Spec sheet information valid only when purchased through Designer Wallcoverings Purchasing</div>
            </div>
        </div>
    </div>

    <script>
        let currentProduct = null;
        let productHandle = null;

        function getProductHandle() {
            const urlParams = new URLSearchParams(window.location.search);
            const paramHandle = urlParams.get('product');
            if (paramHandle) return decodeURIComponent(paramHandle);

            const pageHandle = '{{ page.handle }}';
            if (pageHandle && pageHandle !== 'spec-sheet') {
                return pageHandle.replace(/-spec$/, '');
            }
            return null;
        }

        // Convert to title case
        function toTitleCase(str) {
            return str.toLowerCase().replace(/\b\w/g, l => l.toUpperCase());
        }

        productHandle = getProductHandle();

        if (!productHandle) {
            document.getElementById('content').innerHTML = '<div class="loading"><h1>No Product Specified</h1></div>';
        } else {
            loadProduct();
        }

        async function loadProduct() {
            try {
                const response = await fetch(`/products/${productHandle}.js`);
                if (!response.ok) throw new Error('Product not found');

                currentProduct = await response.json();

                const pageResponse = await fetch(`/products/${productHandle}`);
                const html = await pageResponse.text();

                // Extract metafields from product JSON (if available)
                const metafields = extractMetafieldsFromProduct(currentProduct);

                // Merge with data extracted from HTML and description
                const extractedData = universalExtractor(html, currentProduct.description);
                Object.assign(metafields, extractedData);

                renderProduct(metafields);
                document.getElementById('pdfBtn').style.display = 'block';

            } catch (error) {
                console.error('Error:', error);
                document.getElementById('content').innerHTML = `
                    <div class="loading">
                        <h1>Product Not Found</h1>
                        <p>Could not find product: <strong>${productHandle}</strong></p>
                    </div>
                `;
            }
        }

        function extractMetafieldsFromProduct(product) {
            const data = {};

            // Shopify metafields are not directly available in product.js
            // But we can extract them from the HTML page
            // This function will be populated by universalExtractor
            // Priority: Metafields > Tags > Description

            return data;
        }

        function universalExtractor(html, description) {
            const data = {};

            // Extract from page HTML metafields first (highest priority)
            const pageDiv = document.createElement('div');
            pageDiv.innerHTML = html;

            // Look for metafield data in the HTML
            // Map existing metafield names to display names
            const metafieldMapping = {
                'Width': 'Width',
                'repeat': 'Repeat',
                'Content': 'Composition',
                'Contents': 'Composition',
                'Brand': 'Brand',
                'Color': 'Color',
                'Name of Pattern': 'Extra Info',
                'SubBrand': 'Collection',
                'title': 'Title',
                'title_tag': 'Title Tag'
            };

            // Extract metafields from page HTML
            // Look for metafield values in the page
            const metafieldMatches = html.matchAll(/metafield[s]?['":\s]*{([^}]+)}/gi);
            for (const match of metafieldMatches) {
                try {
                    const jsonStr = '{' + match[1] + '}';
                    const metafields = JSON.parse(jsonStr);
                    for (const [key, value] of Object.entries(metafields)) {
                        const displayKey = metafieldMapping[key] || key;
                        if (value && typeof value === 'string' && value.length > 0) {
                            data[displayKey] = value;
                        }
                    }
                } catch (e) {
                    // Not valid JSON, skip
                }
            }

            // Also check for data-metafield attributes
            const metafieldElements = pageDiv.querySelectorAll('[data-metafield], [data-metafield-key]');
            metafieldElements.forEach(elem => {
                const key = elem.getAttribute('data-metafield-key') || elem.getAttribute('data-metafield');
                const value = elem.textContent.trim();
                if (key && value) {
                    const displayKey = metafieldMapping[key] || key.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase());
                    data[displayKey] = value;
                }
            });

            if (description) {
                const div = document.createElement('div');
                div.innerHTML = description;

                // Extract from tables (but skip if it's a specifications table)
                const tables = div.querySelectorAll('table');
                tables.forEach(table => {
                    // Check if this table is preceded by "Specifications" heading
                    let isSpecTable = false;
                    let prevElement = table.previousElementSibling;

                    // Check previous 3 siblings for "Specifications" heading
                    for (let i = 0; i < 3 && prevElement; i++) {
                        const text = prevElement.textContent.toLowerCase();
                        if (text.includes('specification') || text.trim() === 'specifications') {
                            isSpecTable = true;
                            break;
                        }
                        prevElement = prevElement.previousElementSibling;
                    }

                    // Skip specification tables - we'll display them separately
                    if (isSpecTable) return;

                    const rows = table.querySelectorAll('tr');
                    rows.forEach(row => {
                        const cells = row.querySelectorAll('td, th');
                        if (cells.length >= 2) {
                            let key = cells[0].textContent.trim().replace(/[:\-]+$/, '').trim();
                            let value = cells[1].textContent.trim();
                            if (key && value && value.length < 300 && !key.toLowerCase().includes('specification')) {
                                data[key] = value;
                            }
                        }
                    });
                });

                // Extract from lists (skip if under "Specifications" heading)
                const lists = div.querySelectorAll('ul, ol');
                lists.forEach(list => {
                    // Check if this list is preceded by "Specifications" heading
                    let isSpecList = false;
                    let prevElement = list.previousElementSibling;

                    for (let i = 0; i < 3 && prevElement; i++) {
                        const text = prevElement.textContent.toLowerCase();
                        if (text.includes('specification') || text.trim() === 'specifications') {
                            isSpecList = true;
                            break;
                        }
                        prevElement = prevElement.previousElementSibling;
                    }

                    // Skip specification lists
                    if (isSpecList) return;

                    const items = list.querySelectorAll('li');
                    items.forEach(item => {
                        const text = item.textContent;
                        const colonMatch = text.match(/^([^:]+?):\s*(.+)$/);
                        if (colonMatch) {
                            let key = colonMatch[1].trim();
                            let value = colonMatch[2].trim();
                            if (key && value && value.length < 300) {
                                data[key] = value;
                            }
                        }
                    });
                });

                // Extract from plain text patterns (skip if from Specifications section)
                const plainText = description.replace(/<[^>]*>/g, '\n');
                const lines = plainText.split('\n');

                let inSpecSection = false;

                lines.forEach(line => {
                    const lineLower = line.trim().toLowerCase();

                    // Check if we're entering a specifications section
                    if (lineLower === 'specifications' || lineLower === 'specification') {
                        inSpecSection = true;
                        return;
                    }

                    // Check if we're leaving the specifications section (new heading or significant content)
                    if (inSpecSection && line.trim() && !line.includes(':') && line.length > 30) {
                        inSpecSection = false;
                    }

                    // Skip lines that are in a specifications section
                    if (inSpecSection) return;

                    const match = line.match(/^([A-Za-z][A-Za-z0-9\s\-\/\(\)]+?):\s*(.+)$/);
                    if (match) {
                        let key = match[1].trim();
                        let value = match[2].trim();
                        if (key.length > 2 && key.length < 60 &&
                            value.length > 0 && value.length < 300 &&
                            !value.includes('{') && !value.includes('function') &&
                            !key.toLowerCase().includes('http') &&
                            !value.toLowerCase().includes('click here')) {
                            data[key] = value;
                        }
                    }
                });
            }

            // pageDiv already created above for metafield extraction

            // Extract from <p><strong>Field : </strong>Value</p> pattern
            const paragraphs = pageDiv.querySelectorAll('p');
            paragraphs.forEach(p => {
                const strong = p.querySelector('strong');
                if (strong) {
                    const text = strong.textContent.trim();
                    const colonMatch = text.match(/^(.+?)\s*:\s*$/);
                    if (colonMatch) {
                        const key = colonMatch[1].trim();
                        let value = p.textContent.replace(text, '').trim();
                        // Clean up extra whitespace
                        value = value.replace(/\s+/g, ' ').trim();
                        // Filter out CSS values and invalid data
                        if (key && value && value.length > 0 && value.length < 300 &&
                            !key.toLowerCase().includes('specification') &&
                            !value.includes('%}') && !value.includes('%;') &&
                            !value.includes('px') && !value.includes('em') &&
                            value !== 'auto' && value !== 'none' && value !== '100%') {
                            data[key] = value;
                        }
                    }
                }
            });

            const pageText = pageDiv.textContent || pageDiv.innerText;

            const commonFields = {
                'Width': /Width\s*[:\-]\s*([^\n]+)/i,
                'Repeat': /Repeat\s*[:\-]\s*([^\n]+)/i,
                'Lead Time': /Lead\s+Time\s*[:\-]\s*([^\n]+)/i,
                'Composition': /Composition\s*[:\-]\s*([^\n]+)/i,
                'Use': /Use\s*[:\-]\s*([^\n]+)/i,
                'Weight': /Weight\s*[:\-]\s*([^\n]+)/i,
                'Mfr SKU': /Mfr\s+SKU\s*[:\-]\s*([^\n]+)/i,
                'mfr_sku': /mfr[_\s]sku\s*[:\-]\s*([^\n]+)/i
            };

            for (const [key, pattern] of Object.entries(commonFields)) {
                if (!data[key]) {
                    const match = pageText.match(pattern);
                    if (match && match[1]) {
                        const value = match[1].trim().split(/\n/)[0].trim();
                        if (value && value.length > 0 && value.length < 150) {
                            data[key] = value;
                        }
                    }
                }
            }

            return data;
        }

        // Manufacturer pattern extraction from tags
        const manufacturerPatterns = {
            'Schumacher': { regex: /^\d{5,7}$/, flags: '' },
            'Cole & Son': { regex: /^[\d]+\/[\d]+\.?[cC][sS]\.?\d*$/, flags: '' },
            'Cole and Son': { regex: /^[\d]+\/[\d]+\.?[cC][sS]\.?\d*$/, flags: '' },
            'Patty Madden': { regex: /^pm\d{2}-\d{2}$/i, flags: 'i' },
            'York Wallcoverings': { regex: /^[a-z]{2,4}\d{4,6}$/i, flags: 'i' },
            'Brewster': { regex: /^[a-z]{2,3}-?\d{4,6}$/i, flags: 'i' },
            'Koroseal': { regex: /^[A-Z]{2,4}-[A-Z]{2}\d{2}-\d{2}$/, flags: '' },
            'Hollywood Wallcoverings': { regex: /^[a-z]{3}-\d{5}$/i, flags: 'i' },
            'Graham & Brown': { regex: /^\d{5,6}$/i, flags: '' },
            'Kravet Couture': { regex: /^[Aa][Mm][Ww]\d+\.\d+\.\d+$/, flags: '' },
            'Andrew Martin': { regex: /^[Aa][Mm][Ww]\d+\.\d+\.\d+$/, flags: '' },
            'Brunschwig & Fils': { regex: /^[Pp]\d{7}\.\d{3}\.\d+$/, flags: '' },
            'Brunschwig': { regex: /^[Pp]\d{7}\.\d{3}\.\d+$/, flags: '' }
        };

        function extractMfrPattern(vendor, tags) {
            if (!vendor || !tags || tags.length === 0) return null;

            const config = manufacturerPatterns[vendor];
            if (!config) return null;

            const regex = new RegExp(config.regex.source || config.regex, config.flags);

            for (const tag of tags) {
                const cleanTag = tag.trim();
                if (regex.test(cleanTag)) {
                    return cleanTag;
                }
            }

            return null;
        }

        function renderProduct(metafields) {
            let sku = currentProduct.variants[0]?.sku || 'N/A';

            // Extract manufacturer pattern number from tags
            let mfrPattern = extractMfrPattern(currentProduct.vendor, currentProduct.tags);

            // Fallback: Check metafield
            if (!mfrPattern) {
                mfrPattern = metafields['Mfr SKU'] || metafields['mfr_sku'];
            }

            // Last resort: Extract from SKU (remove DW prefix)
            if (!mfrPattern) {
                mfrPattern = sku.replace(/^DW[A-Z]*-?/i, '');
            }

            // Remove "-sample" or "-Sample" from DW SKU for display
            sku = sku.replace(/-+sample$/i, '').replace(/-+$/g, '');

            // Add vendor and type if available
            if (currentProduct.vendor && !metafields['Brand']) {
                metafields['Brand'] = currentProduct.vendor;
            }
            if (currentProduct.type && !metafields['Type']) {
                metafields['Type'] = currentProduct.type;
            }

            // Extract color, width, repeat, dimensions, and certifications from tags
            let colorName = '';
            let widthFromTag = '';
            let repeatFromTag = '';
            let lengthFromTag = '';
            let useFromTag = '';
            const certifications = [];
            const colors = ['Almond', 'Charcoal', 'Cocoa', 'Flint', 'Khaki', 'Linen', 'Mocha', 'Olive', 'Stone', 'Taupe', 'Umber',
                           'Amber', 'Azure', 'Snow', 'Ivory', 'Cream', 'Beige', 'Gray', 'Grey', 'Black', 'White', 'Brown', 'Blue',
                           'Green', 'Red', 'Yellow', 'Orange', 'Purple', 'Pink', 'Gold', 'Silver', 'Bronze', 'Copper'];

            if (currentProduct.tags && currentProduct.tags.length > 0) {
                currentProduct.tags.forEach(tag => {
                    const tagLower = tag.toLowerCase();

                    // Width extraction - various formats
                    // Format 1: "20.5in" or "54in"
                    let widthMatch = tag.match(/^([\d.]+)\s*in$/i);
                    if (widthMatch && !widthFromTag) {
                        widthFromTag = widthMatch[1] + '"';
                    }
                    // Format 2: "27" Width" or '27" Width x 5 yds'
                    widthMatch = tag.match(/([\d.]+)"\s*[Ww]idth/);
                    if (widthMatch && !widthFromTag) {
                        widthFromTag = widthMatch[1] + '"';
                    }

                    // Repeat extraction - "6"V Repeat" or "11" repeat"
                    const repeatMatch = tag.match(/([\d.]+)"\s*[VvHh]?\s*[Rr]epeat/);
                    if (repeatMatch && !repeatFromTag) {
                        repeatFromTag = repeatMatch[1] + '"';
                    }

                    // Length extraction - "5 yds" or "x 5 yds"
                    const lengthMatch = tag.match(/x?\s*([\d.]+)\s*yds?/i);
                    if (lengthMatch && !lengthFromTag) {
                        lengthFromTag = lengthMatch[1] + ' yds';
                    }

                    // Use extraction - tags that start with "Use" or contain usage info
                    if (tag.match(/^[Uu]se\s*:/)) {
                        useFromTag = tag.replace(/^[Uu]se\s*:\s*/i, '');
                    }

                    // Material tags like "100% vinyl"
                    if (tag.includes('%') && !metafields['Composition']) {
                        metafields['Composition'] = tag;
                    }

                    // Color detection
                    colors.forEach(color => {
                        if (tagLower.includes(color.toLowerCase()) && !colorName) {
                            colorName = tag;
                        }
                    });

                    // Certifications
                    if (tagLower.includes('phthalate') || tagLower.includes('phthalate free')) {
                        certifications.push('Phthalate Free');
                    }
                    if (tagLower.includes('type ii') || tagLower.includes('type 2')) {
                        certifications.push('Type II');
                    }
                    if (tagLower.includes('eco') || tagLower.includes('eco-friendly') || tagLower.includes('eco friendly')) {
                        certifications.push('Eco Friendly');
                    }
                    if (tagLower.includes('high traffic') || tagLower.includes('commercial')) {
                        certifications.push('High Traffic');
                    }
                    if (tagLower.includes('seamless')) {
                        certifications.push('Seamless');
                    }
                    if (tagLower.includes('vinyl')) {
                        certifications.push('Vinyl');
                    }
                });
            }

            // Check variant options for color
            if (!colorName && currentProduct.options) {
                currentProduct.options.forEach(option => {
                    if (option.name.toLowerCase() === 'color' && option.values && option.values.length > 0) {
                        colorName = option.values[0];
                    }
                });
            }

            // Use width from tag if found
            if (widthFromTag && !metafields['Width']) {
                metafields['Width'] = widthFromTag;
            }

            // Use repeat from tag if found
            if (repeatFromTag && !metafields['Repeat']) {
                metafields['Repeat'] = repeatFromTag;
            }

            // Use length from tag if found
            if (lengthFromTag && !metafields['Length']) {
                metafields['Length'] = lengthFromTag;
            }

            // Use usage info from tag if found
            if (useFromTag && !metafields['Use']) {
                metafields['Use'] = useFromTag;
            }

            // Priority order for fields
            const priorityFields = [
                'Brand', 'Collection', 'Designer', 'Manufacturer',
                'Composition', 'Content', 'Material',
                'Width', 'Roll Width',
                'Horizontal Repeat', 'Vertical Repeat', 'Repeat',
                'Weight',
                'Match', 'Pattern Match',
                'Abrasion Resistance', 'Durability',
                'Backing', 'Substrate',
                'Type', 'Product Type',
                'Finish', 'Surface',
                'Style', 'Color', 'Pattern'
            ];

            const certificationFields = [];
            const otherFields = [];

            Object.keys(metafields).forEach(key => {
                if (!priorityFields.includes(key)) {
                    if (key.includes('TB') || key.includes('UFAC') || key.includes('NFPA') ||
                        key.includes('Certified') || key.includes('Compliant') ||
                        key.includes('Fire') || key.includes('Safety') ||
                        key.includes('Class') || key.includes('Standard')) {
                        certificationFields.push(key);
                    } else {
                        otherFields.push(key);
                    }
                }
            });

            const allFields = [...priorityFields, ...certificationFields, ...otherFields,
                              'Lead Time', 'Country of Origin', 'Ships From'];

            let specs = '';
            // Add product title first
            specs += `
                <div class="spec-item">
                    <strong>Title:</strong>
                    <span>${currentProduct.title}</span>
                </div>
            `;
            // Add DW SKU
            specs += `
                <div class="spec-item">
                    <strong>DW SKU:</strong>
                    <span>${sku}</span>
                </div>
            `;

            let itemCount = 2; // Start with 2 (Title + DW SKU)

            // Add color if found
            if (colorName) {
                specs += `
                    <div class="spec-item">
                        <strong>Color:</strong>
                        <span>${colorName}</span>
                    </div>
                `;
                itemCount++;
            }

            const displayedFields = new Set(); // Track displayed fields to avoid duplicates

            allFields.forEach(field => {
                if (metafields[field] && !displayedFields.has(field)) {
                    specs += `
                        <div class="spec-item">
                            <strong>${field}:</strong>
                            <span>${metafields[field]}</span>
                        </div>
                    `;
                    displayedFields.add(field);
                    itemCount++;
                }
            });

            // Add certifications
            if (certifications.length > 0) {
                const uniqueCerts = [...new Set(certifications)];
                uniqueCerts.forEach(cert => {
                    specs += `
                        <div class="spec-item">
                            <strong>${cert}:</strong>
                            <span>Yes</span>
                        </div>
                    `;
                    itemCount++;
                });
            }

            // Add Extra Info with manufacturer pattern number at the end
            specs += `
                <div class="spec-item">
                    <strong>Extra Info:</strong>
                    <span>${mfrPattern}</span>
                </div>
            `;
            itemCount++;

            // Use 2 columns only if more than 15 items
            const columnClass = itemCount > 15 ? 'many-items' : '';

            // Build title with color if available
            let displayTitle = toTitleCase(currentProduct.title);
            if (colorName && !displayTitle.toLowerCase().includes(colorName.toLowerCase())) {
                // Extract the first word (pattern name) and insert color after it
                const words = displayTitle.split(' ');
                if (words.length > 0) {
                    // Insert color in title case after the first word (pattern name)
                    words.splice(1, 0, toTitleCase(colorName));
                    displayTitle = words.join(' ');
                }
            }

            const html = `
                <h1 class="product-title">${displayTitle}</h1>

                <div class="content-wrapper">
                    ${currentProduct.featured_image ?
                        `<img src="${currentProduct.featured_image}" alt="${currentProduct.title}" class="product-img" crossorigin="anonymous">`
                        : ''}

                    <div class="spec-grid ${columnClass}">
                        ${specs}
                    </div>
                </div>
            `;

            document.getElementById('content').innerHTML = html;
            document.title = `${currentProduct.title} - Spec Sheet`;
        }

        async function downloadPDF() {
            const button = document.getElementById('pdfBtn');
            button.textContent = 'Generating PDF...';
            button.disabled = true;

            const element = document.getElementById('pdf-content');
            const sku = currentProduct?.variants[0]?.sku || 'spec';
            const filename = `Designer-Wallcoverings-Los-Angeles-${sku}.pdf`;

            try {
                const canvas = await html2canvas(element, {
                    scale: 2,
                    useCORS: true,
                    allowTaint: false,
                    backgroundColor: '#ffffff',
                    logging: false
                });

                const { jsPDF } = window.jspdf;
                const imgData = canvas.toDataURL('image/png');

                const pdf = new jsPDF({
                    orientation: 'portrait',
                    unit: 'mm',
                    format: 'letter'
                });

                const imgWidth = 216;
                const imgHeight = (canvas.height * imgWidth) / canvas.width;

                pdf.addImage(imgData, 'PNG', 0, 0, imgWidth, imgHeight);
                pdf.save(filename);

                button.textContent = '✓ Downloaded!';
                setTimeout(() => {
                    button.textContent = '📄 Download PDF';
                    button.disabled = false;
                }, 2000);
            } catch (error) {
                console.error('PDF generation failed:', error);
                button.textContent = '✗ Error - Try Again';
                setTimeout(() => {
                    button.textContent = '📄 Download PDF';
                    button.disabled = false;
                }, 2000);
            }
        }

        loadProduct();
    </script>
</body>
</html>