[object Object]

← back to Designer Wallcoverings

security: strip hardcoded dw_admin DSN password -> env-first/passwordless. No rotation/deploy.

b3de17d1518b8b4d2cf58e9789352318e1b0e255 · 2026-05-30 09:49:14 -0700 · Steve

Files touched

Diff

commit b3de17d1518b8b4d2cf58e9789352318e1b0e255
Author: Steve <steve@designerwallcoverings.com>
Date:   Sat May 30 09:49:14 2026 -0700

    security: strip hardcoded dw_admin DSN password -> env-first/passwordless. No rotation/deploy.
---
 DW-Programming/150dpi/server.js.bak                | 1601 --------------------
 DW-Programming/ImportNewSkufromURL/.env.example    |    5 +
 .../app/api/auth/login/route.ts                    |   14 +-
 .../remove-dwwc-dup-specs-graphql.ts.bak           |  173 ---
 .../remove-dwwc-duplicate-specs.ts.bak             |  193 ---
 .../update-all-schumacher-products.ts.bak          |  514 -------
 .../app/api/auth/login-password/route.ts           |   46 +-
 DW-Websites/.gitignore                             |   41 +
 DW-Websites/GoldLeafWallpaper/index.html           |   81 +-
 DW-Websites/GrassclothWallcoverings/.gitignore     |   12 +-
 DW-Websites/GrassclothWallcoverings/index.html     |    1 +
 .../GrassclothWallcoverings/products-shopify-v4.js |  139 +-
 DW-Websites/GrassclothWallcoverings/styles.css     |   56 +-
 DW-Websites/GrassclothWallpaper.com/.gitignore     |   17 +-
 DW-Websites/GrassclothWallpaper.com/index.html     |   72 +
 DW-Websites/GrassclothWallpaper.com/server.js      |   50 +-
 DW-Websites/LosAngelesFabrics                      |    2 +-
 DW-Websites/Novasuede/.gitignore                   |   18 +
 DW-Websites/Novasuede/index.html                   |    1 +
 .../product.liquid.bak-pre-design-coordinate       |  355 -----
 DW-Websites/flockedwallpaper/.gitignore            |   15 +
 DW-Websites/glassbeadedwallpaper/.gitignore        |   15 +
 US-001-IMPLEMENTATION.md.pre-scrub-2026-05-07.bak  |  194 ---
 ai-enrich-arte-fix.js.pre-scrub-2026-05-07.bak     |  116 --
 ai-enrich-gaps2.js.pre-scrub-2026-05-07.bak        |  133 --
 arte-fill-tags.js.pre-scrub-2026-05-07.bak         |  123 --
 enrich-arte-fix.js.pre-scrub-2026-05-07.bak        |  139 --
 enrich-arte-gaps.js.pre-scrub-2026-05-07.bak       |  124 --
 enrich-arte-puppeteer.js.pre-scrub-2026-05-07.bak  |  164 --
 enrich-remaining-13.js.pre-scrub-2026-05-07.bak    |  136 --
 ...ni-classify-texture.js.pre-scrub-2026-05-07.bak |  112 --
 gemini-enrich-arte.js.pre-scrub-2026-05-07.bak     |  120 --
 gemini-enrich-arte9.js.pre-scrub-2026-05-07.bak    |  152 --
 gemini-enrich-gaps.js.pre-scrub-2026-05-07.bak     |  175 ---
 scripts/ncw-cleanup/run-full-20260506-095239.log   |   77 -
 scripts/ncw-cleanup/run-tick2-20260506-100853.log  |   48 -
 36 files changed, 519 insertions(+), 4715 deletions(-)

diff --git a/DW-Programming/150dpi/server.js.bak b/DW-Programming/150dpi/server.js.bak
deleted file mode 100644
index f4064393..00000000
--- a/DW-Programming/150dpi/server.js.bak
+++ /dev/null
@@ -1,1601 +0,0 @@
-const express = require('express');
-const puppeteer = require('puppeteer');
-const cors = require('cors');
-const path = require('path');
-const fs = require('fs').promises;
-const os = require('os');
-const { exec } = require('child_process');
-const { promisify } = require('util');
-const imageDb = require('./database/db');
-const archiver = require('archiver');
-
-const execPromise = promisify(exec);
-
-const app = express();
-const PORT = 3150;
-const COOKIES_PATH = path.join(os.homedir(), '.spoonflower-cookies.json');
-const DOWNLOAD_PATH = path.join(os.tmpdir(), 'spoonflower-downloads');
-const USER_DATA_DIR = path.join(os.homedir(), '.spoonflower-browser-profile');
-const SERVER_START_TIME = new Date();
-
-// Keep browser instance alive between operations
-let persistentBrowser = null;
-let sessionRestoreAttempted = false;
-
-app.use(cors());
-app.use(express.json({ limit: '50mb' })); // Increase limit for large images
-app.use(express.urlencoded({ limit: '50mb', extended: true }));
-app.use(express.static(__dirname));
-
-// Ensure download directory exists
-async function ensureDownloadDir() {
-    try {
-        await fs.mkdir(DOWNLOAD_PATH, { recursive: true });
-    } catch (error) {
-        // Directory already exists
-    }
-}
-
-// Load saved cookies
-async function loadCookies() {
-    try {
-        const cookiesString = await fs.readFile(COOKIES_PATH, 'utf8');
-        return JSON.parse(cookiesString);
-    } catch (error) {
-        return null;
-    }
-}
-
-// Save cookies
-async function saveCookies(cookies) {
-    await fs.writeFile(COOKIES_PATH, JSON.stringify(cookies, null, 2));
-}
-
-// Detect if user is logged in to Spoonflower
-async function detectLoginSuccess(page) {
-    try {
-        // Check 1: URL changed away from login page
-        const currentUrl = page.url();
-        if (!currentUrl.includes('/login') && !currentUrl.includes('/sign-in')) {
-            // Check if we're on a logged-in page (not just redirected to home)
-            const hasLoggedInIndicators = await page.evaluate(() => {
-                // Check for user menu, account links, or logged-in content
-                const hasUserMenu = !!document.querySelector('[data-testid="user-menu"], .user-menu, [aria-label*="account" i], [aria-label*="profile" i]');
-                const hasAccountLink = !!document.querySelector('a[href*="/account"], a[href*="/profile"], a[href*="/designs"]');
-                const hasLogoutButton = !!Array.from(document.querySelectorAll('a, button')).find(el =>
-                    el.textContent.toLowerCase().includes('log out') || el.textContent.toLowerCase().includes('sign out')
-                );
-                const hasLoginButton = !!Array.from(document.querySelectorAll('a, button')).find(el =>
-                    el.textContent.toLowerCase().includes('log in') || el.textContent.toLowerCase().includes('sign in')
-                );
-                
-                // If we have user menu/account link OR logout button, and no login button, we're logged in
-                return (hasUserMenu || hasAccountLink || hasLogoutButton) && !hasLoginButton;
-            });
-            
-            if (hasLoggedInIndicators) {
-                return true;
-            }
-        }
-        
-        // Check 2: Look for logged-in indicators even on login page (in case of redirect back)
-        const loggedIn = await page.evaluate(() => {
-            // Check for user menu, account links, etc.
-            const hasUserMenu = !!document.querySelector('[data-testid="user-menu"], .user-menu, [aria-label*="account" i]');
-            const hasAccountLink = !!document.querySelector('a[href*="/account"], a[href*="/profile"]');
-            const cookies = document.cookie;
-            const hasSessionCookie = cookies.includes('session') || cookies.includes('auth') || cookies.includes('token');
-            
-            return hasUserMenu || hasAccountLink || hasSessionCookie;
-        });
-        
-        return loggedIn;
-    } catch (error) {
-        console.error('Error detecting login status:', error);
-        return false;
-    }
-}
-
-// Clean up browser profile lock
-async function cleanupBrowserProfile() {
-    try {
-        console.log('Cleaning up browser profile...');
-
-        // Kill any Chrome processes using the spoonflower profile
-        try {
-            await execPromise('pkill -f "spoonflower-browser-profile"');
-            console.log('Killed Chrome processes');
-        } catch (e) {
-            // Process might not exist, that's ok
-            console.log('No Chrome processes to kill');
-        }
-
-        // Remove the lock file
-        const lockFile = path.join(USER_DATA_DIR, 'SingletonLock');
-        try {
-            await fs.unlink(lockFile);
-            console.log('Removed SingletonLock file');
-        } catch (e) {
-            // File might not exist, that's ok
-            console.log('No SingletonLock file to remove');
-        }
-
-        // Wait a moment for processes to fully terminate
-        await new Promise(resolve => setTimeout(resolve, 1000));
-
-    } catch (error) {
-        console.log('Cleanup warning (non-critical):', error.message);
-    }
-}
-
-// Attempt to restore browser session from saved profile
-async function restoreBrowserSession() {
-    if (sessionRestoreAttempted) {
-        console.log('Session restore already attempted');
-        return false;
-    }
-
-    sessionRestoreAttempted = true;
-
-    try {
-        // Check if we have a saved browser profile
-        try {
-            await fs.access(USER_DATA_DIR);
-        } catch (e) {
-            console.log('No saved browser profile found');
-            return false;
-        }
-
-        // Check if we have saved cookies
-        const cookies = await loadCookies();
-        if (!cookies || cookies.length === 0) {
-            console.log('No saved cookies found');
-            return false;
-        }
-
-        console.log(`Found saved profile and ${cookies.length} cookies, attempting to restore session...`);
-
-        // Clean up any stale locks before launching
-        await cleanupBrowserProfile();
-
-        // Launch browser with saved profile
-        console.log('Launching browser with saved profile...');
-        persistentBrowser = await puppeteer.launch({
-            headless: false,
-            executablePath: '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
-            userDataDir: USER_DATA_DIR,
-            args: [
-                '--no-sandbox',
-                '--disable-setuid-sandbox',
-                '--disable-dev-shm-usage',
-                '--disable-blink-features=AutomationControlled',
-                '--window-size=1280,900'
-            ],
-            ignoreHTTPSErrors: true,
-            defaultViewport: null
-        });
-
-        console.log('Browser launched successfully with saved profile');
-
-        // Open a page to verify session
-        const page = await persistentBrowser.newPage();
-        await page.goto('https://www.spoonflower.com/en/artists/designs', {
-            waitUntil: 'networkidle2',
-            timeout: 30000
-        });
-
-        // Check if we're actually logged in
-        const isLoggedIn = await page.evaluate(() => {
-            // Check for common logged-in indicators
-            const hasUserMenu = !!document.querySelector('[data-testid="user-menu"], .user-menu, [aria-label*="account" i]');
-            const hasLoginButton = !!Array.from(document.querySelectorAll('a, button')).find(el =>
-                el.textContent.toLowerCase().includes('log in') || el.textContent.toLowerCase().includes('sign in')
-            );
-            return hasUserMenu && !hasLoginButton;
-        });
-
-        if (isLoggedIn) {
-            console.log('✓ Browser session restored successfully - user is logged in');
-            return true;
-        } else {
-            console.log('✗ Browser session restored but user is NOT logged in - please run Setup Login');
-            // Keep browser open but mark as not logged in
-            return false;
-        }
-
-    } catch (error) {
-        console.error('Error restoring browser session:', error.message);
-        // Close browser if it was partially opened
-        if (persistentBrowser && persistentBrowser.isConnected()) {
-            try {
-                await persistentBrowser.close();
-            } catch (e) {
-                // Ignore
-            }
-            persistentBrowser = null;
-        }
-        return false;
-    }
-}
-
-// Manual Login Setup - Opens browser and automatically detects login
-app.post('/api/manual-login-setup', async (req, res) => {
-    let page = null;
-    try {
-        // Close existing browser if any
-        if (persistentBrowser && persistentBrowser.isConnected()) {
-            console.log('Closing existing browser session...');
-            await persistentBrowser.close();
-            persistentBrowser = null;
-        }
-
-        // Clean up any stale browser processes and locks
-        await cleanupBrowserProfile();
-
-        console.log('Launching browser for manual login...');
-
-        // Launch browser in visible mode
-        persistentBrowser = await puppeteer.launch({
-            headless: false, // Visible so user can log in
-            executablePath: '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
-            userDataDir: USER_DATA_DIR, // Persist login across sessions
-            args: [
-                '--no-sandbox',
-                '--disable-setuid-sandbox',
-                '--disable-dev-shm-usage',
-                '--disable-blink-features=AutomationControlled',
-                '--window-size=1280,900'
-            ],
-            ignoreHTTPSErrors: true,
-            defaultViewport: null
-        });
-
-        page = await persistentBrowser.newPage();
-
-        // Navigate to Spoonflower new design page (which requires login)
-        console.log('Opening Spoonflower page...');
-        await page.goto('https://www.spoonflower.com/artists/designs/new', {
-            waitUntil: 'networkidle2',
-            timeout: 30000
-        });
-
-        // Wait for page to load
-        await new Promise(resolve => setTimeout(resolve, 3000));
-
-        // Check if we need to log in (page might redirect to login or show login form)
-        console.log('Checking if login is required...');
-        await new Promise(resolve => setTimeout(resolve, 2000));
-        
-        const currentUrl = page.url();
-        const needsLogin = await page.evaluate(() => {
-            // Check if we see login form or login prompts
-            const hasLoginForm = !!document.querySelector('form[action*="login"], form[action*="sign-in"]');
-            const hasLoginInputs = !!document.querySelector('input[type="email"], input[name="email"]') &&
-                                  !!document.querySelector('input[type="password"]');
-            const hasLoginPrompt = !!Array.from(document.querySelectorAll('h1, h2, h3, p')).find(el => {
-                const text = el.textContent.toLowerCase();
-                return text.includes('log in') || text.includes('sign in') || text.includes('please log in');
-            });
-            
-            return hasLoginForm || (hasLoginInputs && hasLoginPrompt);
-        });
-        
-        // Auto-fill login credentials if login form is present
-        if (needsLogin || currentUrl.includes('/login') || currentUrl.includes('/sign-in')) {
-            console.log('Login form detected. Auto-filling credentials...');
-            try {
-                await page.evaluate(() => {
-                    // Find email input field
-                    const emailInput = document.querySelector('input[type="email"]') ||
-                                     document.querySelector('input[name="email"]') ||
-                                     document.querySelector('input[id*="email" i]') ||
-                                     document.querySelector('input[placeholder*="email" i]');
-                    
-                    // Find password input field
-                    const passwordInput = document.querySelector('input[type="password"]') ||
-                                        document.querySelector('input[name="password"]') ||
-                                        document.querySelector('input[id*="password" i]');
-                    
-                    if (emailInput) {
-                        emailInput.focus();
-                        emailInput.value = 'info@designerwallcoverings.com';
-                        emailInput.dispatchEvent(new Event('input', { bubbles: true }));
-                        emailInput.dispatchEvent(new Event('change', { bubbles: true }));
-                    }
-                    
-                    if (passwordInput) {
-                        passwordInput.focus();
-                        passwordInput.value = '*Spoonaccess911*';
-                        passwordInput.dispatchEvent(new Event('input', { bubbles: true }));
-                        passwordInput.dispatchEvent(new Event('change', { bubbles: true }));
-                    }
-                });
-                
-                // Wait a moment for fields to be filled
-                await new Promise(resolve => setTimeout(resolve, 1000));
-                
-                console.log('Credentials auto-filled! User just needs to solve CAPTCHA (if any) and click login.');
-            } catch (fillError) {
-                console.log('Could not auto-fill credentials (fields may have different selectors):', fillError.message);
-                // Continue anyway - user can still type manually
-            }
-        } else {
-            console.log('Already logged in or on design creation page!');
-        }
-
-        console.log('Browser opened! Waiting for user to complete login (if needed) or confirm access to design creation page...');
-
-        // Monitor for login success with timeout (10 minutes)
-        const TIMEOUT = 10 * 60 * 1000; // 10 minutes
-        const CHECK_INTERVAL = 2000; // Check every 2 seconds
-        const startTime = Date.now();
-        let loginDetected = false;
-
-        while (Date.now() - startTime < TIMEOUT && !loginDetected) {
-            // Check if browser was closed
-            if (!persistentBrowser || !persistentBrowser.isConnected()) {
-                return res.status(400).json({
-                    error: 'Browser was closed',
-                    message: 'Browser window was closed before login completed. Please try again.'
-                });
-            }
-
-            // Check if page is still valid
-            try {
-                const pages = await persistentBrowser.pages();
-                if (pages.length === 0 || !pages.includes(page)) {
-                    // Page was closed, try to get current page
-                    const currentPages = await persistentBrowser.pages();
-                    if (currentPages.length > 0) {
-                        page = currentPages[currentPages.length - 1];
-                    } else {
-                        return res.status(400).json({
-                            error: 'Browser page closed',
-                            message: 'Browser page was closed. Please try again.'
-                        });
-                    }
-                }
-
-                // Check for login success
-                loginDetected = await detectLoginSuccess(page);
-                
-                if (loginDetected) {
-                    console.log('Login detected! Saving cookies...');
-                    
-                    // Get cookies from the logged-in session
-                    const cookies = await page.cookies();
-                    
-                    if (cookies.length === 0) {
-                        // Wait a bit more for cookies to be set
-                        await new Promise(resolve => setTimeout(resolve, 2000));
-                        const cookiesRetry = await page.cookies();
-                        if (cookiesRetry.length === 0) {
-                            return res.status(400).json({
-                                error: 'No cookies found',
-                                message: 'Login detected but no cookies found. Please try logging in again.'
-                            });
-                        }
-                        // Save cookies
-                        await saveCookies(cookiesRetry);
-                        console.log(`Saved ${cookiesRetry.length} cookies to file`);
-                        
-                        return res.json({
-                            success: true,
-                            message: 'Login successful! Cookies saved automatically.',
-                            cookieCount: cookiesRetry.length
-                        });
-                    }
-                    
-                    // Save cookies
-                    await saveCookies(cookies);
-                    console.log(`Saved ${cookies.length} cookies to file`);
-                    
-                    return res.json({
-                        success: true,
-                        message: 'Login successful! Cookies saved automatically.',
-                        cookieCount: cookies.length
-                    });
-                }
-            } catch (checkError) {
-                console.error('Error checking login status:', checkError);
-                // Continue checking unless browser is closed
-                if (!persistentBrowser || !persistentBrowser.isConnected()) {
-                    return res.status(400).json({
-                        error: 'Browser connection lost',
-                        message: 'Browser connection was lost. Please try again.'
-                    });
-                }
-            }
-
-            // Wait before next check
-            await new Promise(resolve => setTimeout(resolve, CHECK_INTERVAL));
-        }
-
-        // Timeout reached
-        if (!loginDetected) {
-            return res.status(408).json({
-                error: 'Login timeout',
-                message: 'Login was not completed within 10 minutes. Please try again.'
-            });
-        }
-
-    } catch (error) {
-        console.error('Error setting up manual login:', error);
-        
-        // Clean up browser if error occurred
-        if (persistentBrowser && persistentBrowser.isConnected()) {
-            try {
-                await persistentBrowser.close();
-            } catch (e) {
-                // Ignore cleanup errors
-            }
-            persistentBrowser = null;
-        }
-        
-        res.status(500).json({
-            error: 'Failed to setup login',
-            details: error.message
-        });
-    }
-});
-
-// Save Login - Call this after user has logged in manually
-app.post('/api/save-login', async (req, res) => {
-    try {
-        if (!persistentBrowser || !persistentBrowser.isConnected()) {
-            return res.status(400).json({
-                error: 'No browser session found. Please click "Setup Manual Login" first.'
-            });
-        }
-
-        const pages = await persistentBrowser.pages();
-        const page = pages[pages.length - 1];
-
-        // Get cookies from the logged-in session
-        const cookies = await page.cookies();
-
-        if (cookies.length === 0) {
-            return res.status(400).json({
-                error: 'No cookies found. Please make sure you are logged in to Spoonflower.'
-            });
-        }
-
-        // Save cookies to file
-        await saveCookies(cookies);
-
-        console.log(`Saved ${cookies.length} cookies to file`);
-
-        res.json({
-            success: true,
-            message: 'Login saved! You can now download and upload designs.',
-            cookieCount: cookies.length
-        });
-
-    } catch (error) {
-        console.error('Error saving login:', error);
-        res.status(500).json({
-            error: 'Failed to save login',
-            details: error.message
-        });
-    }
-});
-
-// Download image from Spoonflower using improved CDP approach
-app.post('/api/download-spoonflower', async (req, res) => {
-    const { url } = req.body;
-
-    if (!url || !url.includes('spoonflower.com')) {
-        return res.status(400).json({ error: 'Invalid Spoonflower URL' });
-    }
-
-    let browser;
-    let page;
-    try {
-        await ensureDownloadDir();
-
-        // Reuse persistent browser if it exists and is connected
-        if (persistentBrowser && persistentBrowser.isConnected()) {
-            console.log('Reusing existing logged-in browser session');
-            browser = persistentBrowser;
-
-            // Reuse the SAME PAGE that's already logged in (don't create new page)
-            const pages = await browser.pages();
-            page = pages[pages.length - 1]; // Use the last page (the one from login)
-
-            console.log(`Found ${pages.length} pages, using page ${pages.length - 1}`);
-        } else {
-            console.log('No active browser session, attempting to restore...');
-
-            // Attempt to restore session from saved profile
-            const restored = await restoreBrowserSession();
-
-            if (restored && persistentBrowser && persistentBrowser.isConnected()) {
-                console.log('Session restored successfully, proceeding with download');
-                browser = persistentBrowser;
-                const pages = await browser.pages();
-                page = pages[pages.length - 1];
-            } else {
-                console.log('Failed to restore session - please run Setup Login first');
-                return res.status(401).json({
-                    error: 'Not logged in',
-                    needsLogin: true,
-                    message: 'Please run Setup Login first to establish browser session'
-                });
-            }
-        }
-
-        // Set download behavior
-        const client = await page.target().createCDPSession();
-        await client.send('Page.setDownloadBehavior', {
-            behavior: 'allow',
-            downloadPath: DOWNLOAD_PATH
-        });
-
-        // Navigate directly to design page (session from persistent profile)
-        console.log('Navigating to design page with persistent session...');
-        await page.goto(url, {
-            waitUntil: 'networkidle2',
-            timeout: 30000
-        });
-
-        // Wait for page to fully load
-        console.log('Waiting for page to load...');
-        await new Promise(resolve => setTimeout(resolve, 3000));
-
-        // Take a screenshot for debugging
-        const screenshotPath = path.join(DOWNLOAD_PATH, 'debug-page.png');
-        await page.screenshot({ path: screenshotPath, fullPage: false });
-        console.log('Screenshot saved to:', screenshotPath);
-
-        // Close any open menus first by clicking elsewhere
-        console.log('Closing any open menus...');
-        await page.evaluate(() => {
-            // Click on the main content area to close menus
-            const main = document.querySelector('main') || document.body;
-            main.click();
-        });
-        await new Promise(resolve => setTimeout(resolve, 1000));
-
-        // Try to find and click File Options button using multiple strategies
-        console.log('Looking for File Options button...');
-        const downloadInfo = await page.evaluate(() => {
-            // Strategy 1: Find by text content "File Options"
-            let button = Array.from(document.querySelectorAll('button')).find(btn =>
-                btn.textContent.includes('File Options')
-            );
-
-            // Strategy 2: Find by text content with more variations
-            if (!button) {
-                button = Array.from(document.querySelectorAll('button')).find(btn =>
-                    btn.textContent.match(/file\s*options/i)
-                );
-            }
-
-            // Strategy 3: Find by aria-label
-            if (!button) {
-                button = document.querySelector('button[aria-label*="File"]') ||
-                         document.querySelector('button[aria-label*="file"]');
-            }
-
-            // Strategy 4: Find chakra menu button (common on Spoonflower)
-            if (!button) {
-                const menuButtons = document.querySelectorAll('button.chakra-menu__menu-button');
-                // Try to find one near the design area
-                for (let btn of menuButtons) {
-                    const text = btn.textContent || btn.getAttribute('aria-label') || '';
-                    if (text.toLowerCase().includes('file') || text.toLowerCase().includes('options')) {
-                        button = btn;
-                        break;
-                    }
-                }
-            }
-
-            // Strategy 5: Find any menu button with 3 dots (⋮) icon
-            if (!button) {
-                button = Array.from(document.querySelectorAll('button')).find(btn =>
-                    btn.textContent.includes('⋮') || btn.textContent.includes('•••') || btn.textContent.includes('...')
-                );
-            }
-
-            // Strategy 6: Find button with SVG icon (common pattern)
-            if (!button) {
-                const buttonsWithSvg = Array.from(document.querySelectorAll('button svg')).map(svg => svg.closest('button'));
-                for (let btn of buttonsWithSvg) {
-                    const ariaLabel = btn?.getAttribute('aria-label') || '';
-                    if (ariaLabel.toLowerCase().includes('file') || ariaLabel.toLowerCase().includes('menu')) {
-                        button = btn;
-                        break;
-                    }
-                }
-            }
-
-            if (!button) {
-                // Debug: List all buttons on page
-                const allButtons = Array.from(document.querySelectorAll('button')).map(btn => ({
-                    text: btn.textContent.substring(0, 50),
-                    ariaLabel: btn.getAttribute('aria-label'),
-                    class: btn.className
-                }));
-                return {
-                    error: 'File Options button not found',
-                    debug: allButtons.slice(0, 20) // First 20 buttons for debugging
-                };
-            }
-
-            // Click it
-            button.click();
-
-            // Wait a moment for menu to appear
-            return { clicked: true };
-        });
-
-        if (downloadInfo.error) {
-            console.error('Available buttons on page:', JSON.stringify(downloadInfo.debug, null, 2));
-            throw new Error('File Options button not found. You may not be logged in or this may not be your design. Please run "Setup Login" first.');
-        }
-
-        console.log('File Options clicked, waiting for menu to appear...');
-
-        // Wait for the menu to actually appear
-        try {
-            await page.waitForSelector('[role="menu"], [role="menuitem"], .chakra-menu__menu-list', {
-                timeout: 5000,
-                visible: true
-            });
-            console.log('Menu appeared!');
-        } catch (err) {
-            console.log('Menu selector not found, waiting with delay...');
-        }
-
-        // Wait longer for menu animation and rendering
-        await new Promise(resolve => setTimeout(resolve, 2000));
-
-        // Get the download link
-        console.log('Finding download link...');
-        const downloadUrl = await page.evaluate(() => {
-            // Strategy 1: Find by role and download attribute
-            let menuItem = document.querySelector('a[role="menuitem"][download]');
-
-            // Strategy 2: Find by text content and download attribute
-            if (!menuItem) {
-                menuItem = Array.from(document.querySelectorAll('a')).find(a =>
-                    a.textContent.includes('Download') && a.hasAttribute('download')
-                );
-            }
-
-            // Strategy 3: Find by text "Download Current File"
-            if (!menuItem) {
-                menuItem = Array.from(document.querySelectorAll('a, button')).find(el =>
-                    el.textContent.includes('Download Current File') ||
-                    el.textContent.includes('Download File')
-                );
-            }
-
-            // Strategy 4: Find any link/button with "download" in text (case insensitive)
-            if (!menuItem) {
-                menuItem = Array.from(document.querySelectorAll('a, button')).find(el =>
-                    el.textContent.toLowerCase().includes('download')
-                );
-            }
-
-            // Strategy 5: Find by href containing download pattern
-            if (!menuItem) {
-                menuItem = Array.from(document.querySelectorAll('a')).find(a =>
-                    a.href && (a.href.includes('download') || a.href.includes('.png') || a.href.includes('.jpg'))
-                );
-            }
-
-            if (!menuItem) {
-                // Debug: List all menu items (not all page links)
-                const menuContainer = document.querySelector('[role="menu"], .chakra-menu__menu-list');
-                let menuDebug = [];
-
-                if (menuContainer) {
-                    menuDebug = Array.from(menuContainer.querySelectorAll('a, button')).map(el => ({
-                        tag: el.tagName,
-                        text: el.textContent.trim().substring(0, 50),
-                        role: el.getAttribute('role'),
-                        download: el.getAttribute('download'),
-                        href: el.href || null
-                    }));
-                } else {
-                    // If no menu found, show all links that might be menu items
-                    menuDebug = Array.from(document.querySelectorAll('[role="menuitem"]')).map(el => ({
-                        tag: el.tagName,
-                        text: el.textContent.trim().substring(0, 50),
-                        role: el.getAttribute('role'),
-                        download: el.getAttribute('download'),
-                        href: el.href || null
-                    }));
-                }
-
-                return {
-                    error: 'Download link not found in menu',
-                    menuFound: !!menuContainer,
-                    debug: menuDebug.length > 0 ? menuDebug : 'No menu items found'
-                };
-            }
-
-            // If it's a button, we need to click it differently
-            const isButton = menuItem.tagName === 'BUTTON';
-
-            return {
-                url: menuItem.href || 'button',
-                filename: menuItem.getAttribute('download') || 'spoonflower_design.png',
-                isButton: isButton,
-                text: menuItem.textContent
-            };
-        });
-
-        if (downloadUrl.error) {
-            console.error('Available menu items:', JSON.stringify(downloadUrl.debug, null, 2));
-            throw new Error(downloadUrl.error);
-        }
-
-        console.log('Download URL found:', downloadUrl.url);
-
-        // Click the download link and monitor for file
-        console.log('Starting download...');
-        await page.evaluate(() => {
-            const menuItem = document.querySelector('a[role="menuitem"][download]') ||
-                            Array.from(document.querySelectorAll('a')).find(a =>
-                                a.textContent.includes('Download') && a.hasAttribute('download')
-                            );
-            if (menuItem) menuItem.click();
-        });
-
-        // Wait for file to download
-        console.log('Waiting for download to complete...');
-        await new Promise(resolve => setTimeout(resolve, 3000));
-
-        // Find the downloaded file
-        const files = await fs.readdir(DOWNLOAD_PATH);
-        const imageFiles = files.filter(f =>
-            (f.endsWith('.png') || f.endsWith('.jpg') || f.endsWith('.jpeg')) &&
-            f !== 'debug-page.png' // Exclude debug screenshot
-        );
-
-        if (imageFiles.length === 0) {
-            throw new Error('No image file downloaded. Check if download started.');
-        }
-
-        // Get the most recent file by modification time (not alphabetically)
-        const fileStats = await Promise.all(
-            imageFiles.map(async (file) => ({
-                name: file,
-                mtime: (await fs.stat(path.join(DOWNLOAD_PATH, file))).mtime
-            }))
-        );
-        fileStats.sort((a, b) => b.mtime - a.mtime); // Sort newest first
-        const downloadedFile = fileStats[0].name;
-        const filePath = path.join(DOWNLOAD_PATH, downloadedFile);
-
-        console.log('Reading downloaded file:', downloadedFile);
-        const imageBuffer = await fs.readFile(filePath);
-        const base64Image = imageBuffer.toString('base64');
-        const contentType = downloadedFile.endsWith('.png') ? 'image/png' : 'image/jpeg';
-
-        // Clean up the downloaded file
-        await fs.unlink(filePath);
-
-        // Don't close the page - we're reusing it for future downloads
-        console.log('Image processed successfully! Browser and page kept open for future downloads.');
-
-        res.json({
-            success: true,
-            image: `data:${contentType};base64,${base64Image}`,
-            filename: downloadUrl.filename
-        });
-
-    } catch (error) {
-        console.error('Error downloading from Spoonflower:', error);
-        // Don't close the page since we're reusing it
-
-        res.status(500).json({
-            error: 'Failed to download from Spoonflower',
-            details: error.message
-        });
-    }
-});
-
-// Upload image back to Spoonflower
-app.post('/api/upload-to-spoonflower', async (req, res) => {
-    const { url, imageData, proposedName } = req.body;
-
-    if (!url || !url.includes('spoonflower.com')) {
-        return res.status(400).json({ error: 'Invalid Spoonflower URL' });
-    }
-
-    if (!imageData) {
-        return res.status(400).json({ error: 'No image data provided' });
-    }
-
-    let browser;
-    try {
-        // Load saved cookies
-        const cookies = await loadCookies();
-        if (!cookies) {
-            return res.status(401).json({
-                error: 'Not logged in',
-                needsLogin: true,
-                message: 'Please setup Spoonflower login first'
-            });
-        }
-
-        await ensureDownloadDir();
-        console.log('Launching browser for upload to:', url);
-
-        // Convert base64 image to buffer and save temporarily
-        const base64Data = imageData.replace(/^data:image\/\w+;base64,/, '');
-        const imageBuffer = Buffer.from(base64Data, 'base64');
-        const uploadFilePath = path.join(DOWNLOAD_PATH, 'upload_temp.png');
-        await fs.writeFile(uploadFilePath, imageBuffer);
-
-        browser = await puppeteer.launch({
-            headless: false, // Keep visible so user can see the upload
-            executablePath: '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
-            args: [
-                '--no-sandbox',
-                '--disable-setuid-sandbox',
-                '--disable-dev-shm-usage'
-            ],
-            ignoreHTTPSErrors: true,
-            defaultViewport: null
-        });
-
-        const page = await browser.newPage();
-
-        // Set cookies
-        await page.setCookie(...cookies);
-
-        console.log('Navigating to Spoonflower page...');
-        await page.goto(url, {
-            waitUntil: 'networkidle2',
-            timeout: 30000
-        });
-
-        // Wait for page to load
-        await new Promise(resolve => setTimeout(resolve, 2000));
-
-        // STEP 1: Edit and save the title FIRST
-        console.log('STEP 1: Editing title to add date...');
-        try {
-            // Click "Edit Title & Search" accordion button
-            const editClicked = await page.evaluate(() => {
-                const editBtn = Array.from(document.querySelectorAll('button')).find(btn =>
-                    btn.textContent.includes('Edit Title') && btn.textContent.includes('Search')
-                );
-                if (!editBtn) return false;
-                editBtn.click();
-                return true;
-            });
-
-            if (editClicked) {
-                await new Promise(resolve => setTimeout(resolve, 1000));
-
-                // Get current date in format M/D/YY (no zero padding, 2-digit year)
-                const today = new Date();
-                const month = today.getMonth() + 1; // 1-12
-                const day = today.getDate(); // 1-31
-                const year = String(today.getFullYear()).slice(-2); // Last 2 digits
-                const dateStr = `${month}/${day}/${year}`;
-
-                // Edit the title by actually typing into the field
-                const currentTitle = await page.evaluate(() => {
-                    const titleInput = document.querySelector('input[name="title"]');
-                    return titleInput ? titleInput.value : null;
-                });
-
-                if (!currentTitle) {
-                    console.log('Could not find title input');
-                } else {
-                    // If proposed name is provided, use it; otherwise use suffix
-                    if (proposedName) {
-                        console.log(`Current title: "${currentTitle}"`);
-                        console.log(`Using proposed name: "${proposedName}"`);
-
-                        // Clear and set new title
-                        await page.click('input[name="title"]');
-                        await new Promise(resolve => setTimeout(resolve, 300));
-
-                        // Select all and delete
-                        await page.evaluate(() => {
-                            const titleInput = document.querySelector('input[name="title"]');
-                            titleInput.select();
-                        });
-                        await page.keyboard.press('Backspace');
-                        await new Promise(resolve => setTimeout(resolve, 200));
-
-                        // Type the new proposed name
-                        await page.type('input[name="title"]', proposedName, { delay: 50 });
-
-                        console.log('Set new title to proposed name');
-                        await new Promise(resolve => setTimeout(resolve, 500));
-                    } else {
-                        const suffix = ` 24in-150dpi Updated ${dateStr} `;
-
-                        // Only append if not already there
-                        if (!currentTitle.includes('24in-150dpi') && !currentTitle.includes(suffix.trim())) {
-                            console.log(`Current title: "${currentTitle}"`);
-                            console.log(`Adding suffix: "${suffix}"`);
-
-                            // Click on the title input to focus it
-                            await page.click('input[name="title"]');
-                            await new Promise(resolve => setTimeout(resolve, 300));
-
-                            // Move cursor to end of input
-                            await page.evaluate(() => {
-                                const titleInput = document.querySelector('input[name="title"]');
-                                titleInput.selectionStart = titleInput.value.length;
-                                titleInput.selectionEnd = titleInput.value.length;
-                            });
-
-                            // Type the suffix character by character
-                            await page.type('input[name="title"]', suffix, { delay: 50 });
-
-                            console.log('Typed suffix into title field');
-                            await new Promise(resolve => setTimeout(resolve, 500));
-                        }
-                    }
-                }
-
-                const titleUpdated = true;
-
-                if (titleUpdated) {
-                    // Wait for form interaction to settle
-                    await new Promise(resolve => setTimeout(resolve, 1000));
-
-                    // Click Save button with better detection
-                    console.log('Looking for Save button...');
-                    const saveClicked = await page.evaluate(() => {
-                        // Try multiple strategies to find the Save button
-                        let saveBtn = document.querySelector('button[type="submit"].chakra-button');
-
-                        if (!saveBtn) {
-                            saveBtn = Array.from(document.querySelectorAll('button[type="submit"]')).find(btn =>
-                                btn.textContent.trim() === 'Save'
-                            );
-                        }
-
-                        if (!saveBtn) {
-                            saveBtn = Array.from(document.querySelectorAll('button')).find(btn =>
-                                btn.textContent.includes('Save') && btn.type === 'submit'
-                            );
-                        }
-
-                        if (saveBtn) {
-                            console.log('Found Save button, clicking...');
-                            saveBtn.click();
-                            return true;
-                        }
-                        return false;
-                    });
-
-                    if (saveClicked) {
-                        console.log('Save button clicked! Waiting for save to complete...');
-                        await new Promise(resolve => setTimeout(resolve, 5000));
-                        console.log('Title saved successfully!');
-                    } else {
-                        console.log('Warning: Save button not found - title may not be saved');
-                    }
-                }
-            }
-        } catch (titleError) {
-            console.log('Warning: Could not update title:', titleError.message);
-            // Continue with image upload even if title update fails
-        }
-
-        // STEP 2: Now replace the image
-        console.log('STEP 2: Replacing image file...');
-
-        // Click File Options button
-        console.log('Looking for File Options button...');
-        const fileOptionsClicked = await page.evaluate(() => {
-            let button = Array.from(document.querySelectorAll('button')).find(btn =>
-                btn.textContent.includes('File Options')
-            );
-            if (!button) {
-                button = document.querySelector('button.chakra-menu__menu-button');
-            }
-            if (!button) return false;
-            button.click();
-            return true;
-        });
-
-        if (!fileOptionsClicked) {
-            throw new Error('File Options button not found');
-        }
-
-        console.log('Waiting for menu...');
-        await new Promise(resolve => setTimeout(resolve, 1500));
-
-        // Click "Replace Current File"
-        console.log('Clicking Replace Current File...');
-        const replaceClicked = await page.evaluate(() => {
-            const replaceBtn = Array.from(document.querySelectorAll('button')).find(btn =>
-                btn.textContent.includes('Replace Current File')
-            );
-            if (!replaceBtn) return false;
-            replaceBtn.click();
-            return true;
-        });
-
-        if (!replaceClicked) {
-            throw new Error('Replace Current File button not found');
-        }
-
-        // Wait for confirmation dialog
-        await new Promise(resolve => setTimeout(resolve, 1000));
-
-        // Click "Yes, continue" on confirmation dialog
-        console.log('Confirming replacement...');
-        await page.evaluate(() => {
-            const yesButton = Array.from(document.querySelectorAll('button')).find(btn =>
-                btn.textContent.includes('Yes, continue') || btn.textContent.includes('continue')
-            );
-            if (yesButton) yesButton.click();
-        });
-
-        // Wait for file input to appear
-        await new Promise(resolve => setTimeout(resolve, 1000));
-
-        // Find and upload file
-        console.log('Uploading file...');
-        const fileInput = await page.$('input[type="file"]');
-        if (!fileInput) {
-            throw new Error('File input not found');
-        }
-
-        await fileInput.uploadFile(uploadFilePath);
-
-        // Wait for upload to process
-        console.log('Upload complete! Waiting for processing...');
-        await new Promise(resolve => setTimeout(resolve, 3000));
-
-        // Clean up temp file
-        await fs.unlink(uploadFilePath);
-
-        console.log('Upload successful! Browser kept open for confirmation.');
-
-        res.json({
-            success: true,
-            message: 'Image uploaded to Spoonflower and title updated! Check the browser window.',
-            keepBrowserOpen: true
-        });
-
-        // Note: Browser stays open so user can see the result
-        // They can close it manually
-
-    } catch (error) {
-        console.error('Error uploading to Spoonflower:', error);
-
-        try {
-            if (browser) await browser.close();
-        } catch (closeError) {
-            console.log('Browser already closed');
-        }
-
-        res.status(500).json({
-            error: 'Failed to upload to Spoonflower',
-            details: error.message
-        });
-    }
-});
-
-// Check login status by checking browser session and cookies
-app.get('/api/check-login', async (req, res) => {
-    try {
-        // First check if we have an active browser session
-        if (persistentBrowser && persistentBrowser.isConnected()) {
-            return res.json({
-                loggedIn: true,
-                browserActive: true,
-                message: 'Browser session active'
-            });
-        }
-
-        // Check if we have saved cookies and profile
-        const cookies = await loadCookies();
-        let hasProfile = false;
-        try {
-            await fs.access(USER_DATA_DIR);
-            hasProfile = true;
-        } catch (e) {
-            // No profile
-        }
-
-        if (!cookies || cookies.length === 0) {
-            return res.json({
-                loggedIn: false,
-                browserActive: false,
-                hasProfile: hasProfile,
-                message: 'Not logged in - please run Setup Login'
-            });
-        }
-
-        // Check if cookies are still valid (not expired)
-        const now = Date.now() / 1000;
-        const validCookies = cookies.filter(cookie => {
-            // Session cookies (no expires field) are always valid until browser closes
-            if (!cookie.expires) return true;
-            // Check if cookie hasn't expired
-            return cookie.expires > now;
-        });
-
-        const canRestore = validCookies.length > 0 && hasProfile;
-
-        res.json({
-            loggedIn: canRestore,
-            browserActive: false,
-            canRestore: canRestore,
-            cookieCount: validCookies.length,
-            hasProfile: hasProfile,
-            message: canRestore ?
-                'Session can be restored (browser will auto-launch on next operation)' :
-                'Session expired - please run Setup Login again'
-        });
-    } catch (error) {
-        res.json({
-            loggedIn: false,
-            browserActive: false,
-            message: 'Not logged in'
-        });
-    }
-});
-
-// Server info
-app.get('/api/server-info', (req, res) => {
-    res.json({
-        startTime: SERVER_START_TIME.toISOString(),
-        uptime: process.uptime()
-    });
-});
-
-// ===== IMAGE DATABASE API =====
-
-// GET all images (with optional status filter)
-app.get('/api/images', (req, res) => {
-    try {
-        const status = req.query.status || 'active';
-        const images = imageDb.getAllImages(status);
-        res.json(images);
-    } catch (error) {
-        console.error('Error fetching images:', error);
-        res.status(500).json({ error: 'Failed to fetch images', details: error.message });
-    }
-});
-
-// GET single image by ID
-app.get('/api/images/:id', (req, res) => {
-    try {
-        const image = imageDb.getImageById(parseInt(req.params.id));
-        if (!image) {
-            return res.status(404).json({ error: 'Image not found' });
-        }
-        res.json(image);
-    } catch (error) {
-        console.error('Error fetching image:', error);
-        res.status(500).json({ error: 'Failed to fetch image', details: error.message });
-    }
-});
-
-// POST create new image
-app.post('/api/images', (req, res) => {
-    try {
-        const { 
-            original_filename, 
-            original_path, 
-            converted_path, 
-            width, 
-            height, 
-            tags,
-            extracted_colors,
-            original_image_data,
-            converted_image_data,
-            conversion_settings,
-            spoonflower_url
-        } = req.body;
-        if (!original_filename) {
-            return res.status(400).json({ error: 'original_filename is required' });
-        }
-        const image = imageDb.createImage({
-            original_filename,
-            original_path,
-            converted_path,
-            width,
-            height,
-            tags: tags || [],
-            extracted_colors: extracted_colors || [],
-            original_image_data: original_image_data || null,
-            converted_image_data: converted_image_data || null,
-            conversion_settings: conversion_settings || null,
-            spoonflower_url: spoonflower_url || null
-        });
-        res.status(201).json(image);
-    } catch (error) {
-        console.error('Error creating image:', error);
-        res.status(500).json({ error: 'Failed to create image', details: error.message });
-    }
-});
-
-// PUT update image (including tags)
-app.put('/api/images/:id', (req, res) => {
-    try {
-        const id = parseInt(req.params.id);
-        const existing = imageDb.getImageById(id);
-        if (!existing) {
-            return res.status(404).json({ error: 'Image not found' });
-        }
-        const updated = imageDb.updateImage(id, req.body);
-        res.json(updated);
-    } catch (error) {
-        console.error('Error updating image:', error);
-        res.status(500).json({ error: 'Failed to update image', details: error.message });
-    }
-});
-
-// DELETE image
-app.delete('/api/images/:id', (req, res) => {
-    try {
-        const id = parseInt(req.params.id);
-        const existing = imageDb.getImageById(id);
-        if (!existing) {
-            return res.status(404).json({ error: 'Image not found' });
-        }
-        imageDb.deleteImage(id);
-        res.json({ success: true, message: 'Image deleted' });
-    } catch (error) {
-        console.error('Error deleting image:', error);
-        res.status(500).json({ error: 'Failed to delete image', details: error.message });
-    }
-});
-
-// PATCH archive image
-app.patch('/api/images/:id/archive', (req, res) => {
-    try {
-        const id = parseInt(req.params.id);
-        const existing = imageDb.getImageById(id);
-        if (!existing) {
-            return res.status(404).json({ error: 'Image not found' });
-        }
-        const updated = imageDb.archiveImage(id);
-        res.json(updated);
-    } catch (error) {
-        console.error('Error archiving image:', error);
-        res.status(500).json({ error: 'Failed to archive image', details: error.message });
-    }
-});
-
-// PATCH restore image
-app.patch('/api/images/:id/restore', (req, res) => {
-    try {
-        const id = parseInt(req.params.id);
-        const existing = imageDb.getImageById(id);
-        if (!existing) {
-            return res.status(404).json({ error: 'Image not found' });
-        }
-        const updated = imageDb.restoreImage(id);
-        res.json(updated);
-    } catch (error) {
-        console.error('Error restoring image:', error);
-        res.status(500).json({ error: 'Failed to restore image', details: error.message });
-    }
-});
-
-// GET all unique tags with counts
-app.get('/api/tags', (req, res) => {
-    try {
-        const tags = imageDb.getAllTags();
-        res.json(tags);
-    } catch (error) {
-        console.error('Error fetching tags:', error);
-        res.status(500).json({ error: 'Failed to fetch tags', details: error.message });
-    }
-});
-
-// POST export selected images as ZIP
-app.post('/api/images/export-zip', (req, res) => {
-    try {
-        const { imageIds, includeOriginal = true, includeConverted = true } = req.body;
-
-        if (!imageIds || !Array.isArray(imageIds) || imageIds.length === 0) {
-            return res.status(400).json({ error: 'imageIds array is required' });
-        }
-
-        // Create timestamp for filename
-        const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);
-        const zipFilename = `150dpi-export-${timestamp}.zip`;
-
-        // Set response headers
-        res.setHeader('Content-Type', 'application/zip');
-        res.setHeader('Content-Disposition', `attachment; filename="${zipFilename}"`);
-
-        // Create archiver instance
-        const archive = archiver('zip', {
-            zlib: { level: 9 } // Maximum compression
-        });
-
-        // Handle archiver warnings and errors
-        archive.on('warning', (err) => {
-            if (err.code === 'ENOENT') {
-                console.warn('Archiver warning:', err);
-            } else {
-                throw err;
-            }
-        });
-
-        archive.on('error', (err) => {
-            console.error('Archiver error:', err);
-            res.status(500).json({ error: 'Error creating ZIP', details: err.message });
-        });
-
-        // Pipe archive to response
-        archive.pipe(res);
-
-        // Add images to ZIP
-        let filesAdded = 0;
-        for (const imageId of imageIds) {
-            const image = imageDb.getImageById(parseInt(imageId));
-            if (!image) {
-                console.warn(`Image ${imageId} not found, skipping`);
-                continue;
-            }
-
-            const baseName = image.original_filename.replace(/\.[^/.]+$/, ''); // Remove extension
-
-            if (includeOriginal && image.original_image_data) {
-                const buffer = Buffer.from(image.original_image_data.replace(/^data:image\/\w+;base64,/, ''), 'base64');
-                archive.append(buffer, { name: `original/${image.original_filename}` });
-                filesAdded++;
-            }
-
-            if (includeConverted && image.converted_image_data) {
-                const buffer = Buffer.from(image.converted_image_data.replace(/^data:image\/\w+;base64,/, ''), 'base64');
-                const ext = image.converted_image_data.match(/^data:image\/(\w+);base64,/)?.[1] || 'png';
-                archive.append(buffer, { name: `converted/${baseName}-150dpi.${ext}` });
-                filesAdded++;
-            }
-        }
-
-        if (filesAdded === 0) {
-            return res.status(404).json({ error: 'No images found to export' });
-        }
-
-        // Finalize the archive
-        archive.finalize();
-
-    } catch (error) {
-        console.error('Error exporting ZIP:', error);
-        res.status(500).json({ error: 'Failed to export ZIP', details: error.message });
-    }
-});
-
-// Upload NEW design to Spoonflower (not updating existing)
-app.post('/api/upload-new-design', async (req, res) => {
-    const { imageData, proposedName } = req.body;
-
-    if (!imageData) {
-        return res.status(400).json({ error: 'No image data provided' });
-    }
-
-    try {
-        // Reuse persistent browser if it exists and is connected
-        if (!persistentBrowser || !persistentBrowser.isConnected()) {
-            console.log('No active browser session, attempting to restore...');
-
-            // Attempt to restore session from saved profile
-            const restored = await restoreBrowserSession();
-
-            if (!restored || !persistentBrowser || !persistentBrowser.isConnected()) {
-                console.log('Failed to restore session - please run Setup Login first');
-                return res.status(401).json({
-                    error: 'Not logged in',
-                    needsLogin: true,
-                    message: 'Please run Setup Login first to establish browser session'
-                });
-            }
-        }
-
-        console.log('Using existing logged-in browser session for new design upload');
-        const browser = persistentBrowser;
-
-        // Reuse existing page or create new one
-        const pages = await browser.pages();
-        const page = pages[pages.length - 1];
-
-        // Convert base64 to buffer and save temporarily
-        const base64Data = imageData.replace(/^data:image\/\w+;base64,/, '');
-        const imageBuffer = Buffer.from(base64Data, 'base64');
-
-        await ensureDownloadDir();
-        const uploadFilePath = path.join(DOWNLOAD_PATH, 'upload_new_design.png');
-        await fs.writeFile(uploadFilePath, imageBuffer);
-        console.log('Saved image to:', uploadFilePath);
-
-        // Navigate to new design upload page
-        console.log('Navigating to /artists/designs/new...');
-        await page.goto('https://www.spoonflower.com/en/artists/designs/new', {
-            waitUntil: 'networkidle2',
-            timeout: 30000
-        });
-
-        // Wait for page to load
-        await new Promise(resolve => setTimeout(resolve, 2000));
-
-        // Click the upload button to show the file input
-        console.log('Checking for upload button...');
-        const uploadButtonClicked = await page.evaluate(() => {
-            const uploadBtn = Array.from(document.querySelectorAll('button')).find(btn =>
-                btn.textContent.includes('Upload') || btn.textContent.includes('Choose')
-            );
-            if (uploadBtn) {
-                uploadBtn.click();
-                return true;
-            }
-            return false;
-        });
-
-        if (uploadButtonClicked) {
-            console.log('Clicked upload button, waiting for form...');
-            await new Promise(resolve => setTimeout(resolve, 2000));
-        }
-
-        // Find and use the file input
-        console.log('Looking for file input...');
-        const fileInput = await page.$('input[type="file"]');
-
-        if (!fileInput) {
-            console.log('File input not found immediately, waiting...');
-            await new Promise(resolve => setTimeout(resolve, 2000));
-
-            // Try clicking on the upload area
-            console.log('Trying to click on upload area...');
-            await page.evaluate(() => {
-                const uploadArea = document.querySelector('[data-testid="upload-area"], .upload-zone, .chakra-button');
-                if (uploadArea) uploadArea.click();
-            });
-            await new Promise(resolve => setTimeout(resolve, 1000));
-        }
-
-        const retryFileInput = await page.$('input[type="file"]');
-        if (!retryFileInput) {
-            throw new Error('File input not found on upload page. Please check the browser window and upload manually if needed.');
-        }
-
-        // Upload the file
-        console.log('Found file input, uploading file...');
-        await retryFileInput.uploadFile(uploadFilePath);
-        console.log('File uploaded! Waiting for processing...');
-
-        // Wait for upload to process
-        await new Promise(resolve => setTimeout(resolve, 4000));
-
-        // CRITICAL: Handle Terms of Service modal if it appears
-        console.log('Checking for Terms of Service modal...');
-        const tosAccepted = await page.evaluate(() => {
-            // Look for the "Agree & Continue" button in the TOS modal
-            const agreeButton = Array.from(document.querySelectorAll('button')).find(btn =>
-                btn.textContent.includes('Agree') && btn.textContent.includes('Continue')
-            );
-
-            if (agreeButton) {
-                console.log('Found "Agree & Continue" button, clicking...');
-                agreeButton.click();
-                return true;
-            }
-            return false;
-        });
-
-        if (tosAccepted) {
-            console.log('Clicked "Agree & Continue" button');
-            await new Promise(resolve => setTimeout(resolve, 3000));
-        } else {
-            console.log('No Terms of Service modal found (may have been previously accepted)');
-        }
-
-        // Set proposed name if provided
-        if (proposedName) {
-            console.log(`Setting proposed name: ${proposedName}`);
-            await page.evaluate((name) => {
-                const titleInput = document.querySelector('input[name="title"], input[placeholder*="title" i]');
-                if (titleInput) {
-                    titleInput.value = name;
-                    titleInput.dispatchEvent(new Event('input', { bubbles: true }));
-                }
-            }, proposedName);
-            await new Promise(resolve => setTimeout(resolve, 500));
-        }
-
-        // Wait for form to be ready
-        console.log('Waiting for upload to process and form to be ready...');
-        await new Promise(resolve => setTimeout(resolve, 3000));
-
-        // Check for required form fields and fill if needed
-        console.log('Checking for required form fields...');
-        await page.evaluate(() => {
-            // Fill any required fields with default values if empty
-            const requiredInputs = document.querySelectorAll('input[required], select[required]');
-            requiredInputs.forEach(input => {
-                if (!input.value && input.type !== 'file') {
-                    if (input.type === 'checkbox') {
-                        input.checked = true;
-                    } else {
-                        input.value = 'Default';
-                    }
-                }
-            });
-        });
-
-        // Find and click Submit/Save button
-        console.log('Looking for Submit/Save button...');
-        const submitClicked = await page.evaluate(() => {
-            let submitBtn = document.querySelector('button[type="submit"]');
-
-            if (!submitBtn) {
-                submitBtn = Array.from(document.querySelectorAll('button')).find(btn =>
-                    btn.textContent.includes('Submit') ||
-                    btn.textContent.includes('Save') ||
-                    btn.textContent.includes('Create')
-                );
-            }
-
-            if (submitBtn) {
-                submitBtn.click();
-                return true;
-            }
-            return false;
-        });
-
-        if (!submitClicked) {
-            throw new Error('Submit button not found');
-        }
-
-        console.log('Submit button clicked! Waiting for design creation...');
-        await new Promise(resolve => setTimeout(resolve, 5000));
-
-        // Get the new design URL
-        const currentUrl = page.url();
-        console.log('Current URL after submit:', currentUrl);
-
-        res.json({
-            success: true,
-            message: 'Design uploaded successfully',
-            url: currentUrl
-        });
-
-    } catch (error) {
-        console.error('Error uploading new design:', error);
-        res.status(500).json({
-            error: 'Failed to upload new design',
-            details: error.message
-        });
-    }
-});
-
-// Health check
-app.get('/api/health', (req, res) => {
-    res.json({ status: 'ok', message: '150 DPI Converter API is running' });
-});
-
-// Startup cleanup to remove stale browser locks
-async function startupCleanup() {
-    console.log('Startup: Cleaning up any stale browser locks...');
-    await cleanupBrowserProfile();
-    console.log('Startup: Ready for operations');
-}
-app.listen(PORT, async () => {
-    console.log(`
-    await startupCleanup();
-╔════════════════════════════════════════════╗
-║   150 DPI Converter Server Running         ║
-║                                            ║
-║   URL: http://localhost:${PORT}            ║
-║   API: http://localhost:${PORT}/api        ║
-╚════════════════════════════════════════════╝
-    `);
-
-    // Clean up any stale browser profile locks on startup
-    console.log('\nStartup: Cleaning up any stale browser locks...');
-    await cleanupBrowserProfile();
-    console.log('Startup: Ready for operations\n');
-});
diff --git a/DW-Programming/ImportNewSkufromURL/.env.example b/DW-Programming/ImportNewSkufromURL/.env.example
index eda65df1..c8a099f9 100644
--- a/DW-Programming/ImportNewSkufromURL/.env.example
+++ b/DW-Programming/ImportNewSkufromURL/.env.example
@@ -1,3 +1,8 @@
+# Basic-auth credentials for /api/auth/login in "user:password" form.
+# REQUIRED at boot — server throws if missing. MUST be set in prod .env to
+# a freshly-rotated secret (do NOT reuse the legacy DW2025Secure! value).
+DW_BASIC_AUTH=admin:CHANGE-ME-BEFORE-DEPLOY
+
 # Hugging Face API Key (optional - for higher rate limits)
 # Get yours at https://huggingface.co/settings/tokens
 HUGGINGFACE_API_KEY=
diff --git a/DW-Programming/ImportNewSkufromURL/app/api/auth/login/route.ts b/DW-Programming/ImportNewSkufromURL/app/api/auth/login/route.ts
index 6a8c063f..b248a6fb 100644
--- a/DW-Programming/ImportNewSkufromURL/app/api/auth/login/route.ts
+++ b/DW-Programming/ImportNewSkufromURL/app/api/auth/login/route.ts
@@ -1,7 +1,17 @@
 import { NextRequest, NextResponse } from 'next/server';
 
-const USERNAME = 'admin';
-const PASSWORD = 'DW2025Secure!';
+// Credentials must be supplied via DW_BASIC_AUTH in "user:password" form.
+// Fail-closed at module load — never default to a hardcoded literal.
+const DW_BASIC_AUTH = process.env.DW_BASIC_AUTH;
+if (!DW_BASIC_AUTH) {
+  throw new Error('DW_BASIC_AUTH env var required for auth — set in .env before boot');
+}
+const colonIdx = DW_BASIC_AUTH.indexOf(':');
+if (colonIdx <= 0 || colonIdx === DW_BASIC_AUTH.length - 1) {
+  throw new Error('DW_BASIC_AUTH must be in "user:password" form');
+}
+const USERNAME = DW_BASIC_AUTH.slice(0, colonIdx);
+const PASSWORD = DW_BASIC_AUTH.slice(colonIdx + 1);
 
 export async function POST(request: NextRequest) {
   try {
diff --git a/DW-Programming/ImportNewSkufromURL/remove-dwwc-dup-specs-graphql.ts.bak b/DW-Programming/ImportNewSkufromURL/remove-dwwc-dup-specs-graphql.ts.bak
deleted file mode 100644
index 5976958e..00000000
--- a/DW-Programming/ImportNewSkufromURL/remove-dwwc-dup-specs-graphql.ts.bak
+++ /dev/null
@@ -1,173 +0,0 @@
-/**
- * Remove duplicate "Specifications" sections from DWWC products using GraphQL
- */
-
-import { shopifyGraphQLRequest } from './lib/shopify';
-
-interface Product {
-  id: string;
-  title: string;
-  description: string;
-  handle: string;
-}
-
-function cleanDuplicateSpecs(bodyHtml: string): { cleaned: string; removed: boolean } {
-  let cleaned = bodyHtml;
-  let removed = false;
-
-  // Remove duplicate <h3>Specifications</h3> sections - keep only FIRST occurrence
-  const specsMatches = cleaned.match(/<h3>Specifications<\/h3>/gi);
-  if (specsMatches && specsMatches.length > 1) {
-    console.log(`    Found ${specsMatches.length} "Specifications" headers - keeping only first`);
-
-    const firstIndex = cleaned.indexOf('<h3>Specifications</h3>');
-    let tempCleaned = cleaned.substring(0, firstIndex);
-    let remaining = cleaned.substring(firstIndex);
-
-    // Keep the first specs section
-    const firstSpecsEnd = remaining.indexOf('</ul>') + 5;
-    tempCleaned += remaining.substring(0, firstSpecsEnd);
-    remaining = remaining.substring(firstSpecsEnd);
-
-    // Remove all subsequent specs sections
-    remaining = remaining.replace(/<h3>Specifications<\/h3>\s*<ul>[\s\S]*?<\/ul>/gi, '');
-
-    cleaned = tempCleaned + remaining;
-    removed = true;
-  }
-
-  // Remove <h3>Product Information</h3> sections (redundant)
-  if (cleaned.includes('<h3>Product Information</h3>') || cleaned.includes('<h4>Product Information</h4>')) {
-    console.log(`    Removing "Product Information" section`);
-    cleaned = cleaned.replace(/<h[34]>Product\s+Information<\/h[34]>\s*<ul>[\s\S]*?<\/ul>/gi, '');
-    removed = true;
-  }
-
-  // Clean up extra whitespace
-  cleaned = cleaned.replace(/\n\s*\n\s*\n/g, '\n\n').trim();
-
-  return { cleaned, removed };
-}
-
-async function getAllDWWCProducts(): Promise<Product[]> {
-  console.log('📦 Fetching all DWWC products via GraphQL...\n');
-
-  const allProducts: Product[] = [];
-  let hasNextPage = true;
-  let cursor: string | null = null;
-
-  while (hasNextPage) {
-    const query = `
-      query GetProducts($cursor: String) {
-        products(first: 50, after: $cursor, query: "vendor:'Designer Wallcoverings'") {
-          edges {
-            node {
-              id
-              title
-              description
-              handle
-            }
-            cursor
-          }
-          pageInfo {
-            hasNextPage
-          }
-        }
-      }
-    `;
-
-    const response = await shopifyGraphQLRequest(query, { cursor });
-
-    const edges = response.data.products.edges;
-    for (const edge of edges) {
-      allProducts.push(edge.node);
-    }
-
-    hasNextPage = response.data.products.pageInfo.hasNextPage;
-    cursor = edges.length > 0 ? edges[edges.length - 1].cursor : null;
-
-    console.log(`  Fetched ${allProducts.length} products so far...`);
-  }
-
-  console.log(`\n✅ Total products fetched: ${allProducts.length}\n`);
-  return allProducts;
-}
-
-async function updateProductDescription(productId: string, newDescription: string): Promise<void> {
-  const mutation = `
-    mutation UpdateProduct($input: ProductInput!) {
-      productUpdate(input: $input) {
-        product {
-          id
-          description
-        }
-        userErrors {
-          field
-          message
-        }
-      }
-    }
-  `;
-
-  const response = await shopifyGraphQLRequest(mutation, {
-    input: {
-      id: productId,
-      description: newDescription,
-    },
-  });
-
-  if (response.data.productUpdate.userErrors.length > 0) {
-    throw new Error(JSON.stringify(response.data.productUpdate.userErrors));
-  }
-}
-
-async function main() {
-  console.log('🧹 DWWC Description Cleanup - Remove Duplicate Specifications\n');
-  console.log('='.repeat(80) + '\n');
-
-  const products = await getAllDWWCProducts();
-
-  let processedCount = 0;
-  let updatedCount = 0;
-  let errorCount = 0;
-
-  for (const product of products) {
-    processedCount++;
-
-    console.log(`[${processedCount}/${products.length}] ${product.title}`);
-
-    if (!product.description) {
-      console.log('  ⏭️  Skipping - no description\n');
-      continue;
-    }
-
-    const { cleaned, removed } = cleanDuplicateSpecs(product.description);
-
-    if (!removed) {
-      console.log('  ✓ No duplicates found\n');
-      continue;
-    }
-
-    try {
-      await updateProductDescription(product.id, cleaned);
-      updatedCount++;
-      console.log('  ✅ Updated successfully\n');
-    } catch (error) {
-      errorCount++;
-      console.error(`  ❌ Error: ${error}\n`);
-    }
-
-    // Rate limiting: 2 requests per second
-    await new Promise(resolve => setTimeout(resolve, 500));
-  }
-
-  console.log('='.repeat(80));
-  console.log('\n📊 FINAL SUMMARY:');
-  console.log(`  Total products: ${products.length}`);
-  console.log(`  Updated: ${updatedCount}`);
-  console.log(`  Errors: ${errorCount}`);
-  console.log(`  No changes needed: ${products.length - updatedCount - errorCount}`);
-  console.log('\n✅ Cleanup complete!');
-}
-
-main().catch(console.error);
diff --git a/DW-Programming/ImportNewSkufromURL/remove-dwwc-duplicate-specs.ts.bak b/DW-Programming/ImportNewSkufromURL/remove-dwwc-duplicate-specs.ts.bak
deleted file mode 100644
index 02f21a8e..00000000
--- a/DW-Programming/ImportNewSkufromURL/remove-dwwc-duplicate-specs.ts.bak
+++ /dev/null
@@ -1,193 +0,0 @@
-/**
- * Remove duplicate "Specifications" or "Product Information" sections
- * from DWWC product descriptions
- */
-
-import 'dotenv/config';
-
-const SHOPIFY_STORE = process.env.SHOPIFY_STORE_DOMAIN!;
-const SHOPIFY_TOKEN = process.env.SHOPIFY_ADMIN_ACCESS_TOKEN!;
-const SHOPIFY_API_VERSION = process.env.SHOPIFY_ADMIN_API_VERSION || '2024-07';
-
-interface ShopifyProduct {
-  id: string;
-  title: string;
-  body_html: string;
-  vendor: string;
-  handle: string;
-}
-
-async function getAllDWWCProducts(): Promise<ShopifyProduct[]> {
-  console.log('📦 Fetching all DWWC products...\n');
-
-  const allProducts: ShopifyProduct[] = [];
-  let hasNextPage = true;
-  let pageInfo: string | null = null;
-
-  while (hasNextPage) {
-    const url = pageInfo
-      ? `https://${SHOPIFY_STORE}/admin/api/${SHOPIFY_API_VERSION}/products.json?vendor=Designer%20Wallcoverings&limit=250&page_info=${pageInfo}`
-      : `https://${SHOPIFY_STORE}/admin/api/${SHOPIFY_API_VERSION}/products.json?vendor=Designer%20Wallcoverings&limit=250`;
-
-    const response = await fetch(url, {
-      headers: {
-        'X-Shopify-Access-Token': SHOPIFY_TOKEN,
-      },
-    });
-
-    if (!response.ok) {
-      throw new Error(`Failed to fetch products: ${response.statusText}`);
-    }
-
-    const data = await response.json();
-    allProducts.push(...data.products);
-
-    // Check for pagination
-    const linkHeader = response.headers.get('Link');
-    if (linkHeader && linkHeader.includes('rel="next"')) {
-      const match = linkHeader.match(/page_info=([^>]+)>; rel="next"/);
-      pageInfo = match ? match[1] : null;
-      hasNextPage = !!pageInfo;
-    } else {
-      hasNextPage = false;
-    }
-
-    console.log(`  Fetched ${allProducts.length} products so far...`);
-  }
-
-  console.log(`\n✅ Total products fetched: ${allProducts.length}\n`);
-  return allProducts;
-}
-
-function cleanDuplicateSpecs(bodyHtml: string): { cleaned: string; removed: boolean } {
-  let cleaned = bodyHtml;
-  let removed = false;
-
-  // Pattern 1: Remove duplicate <h3>Specifications</h3> sections
-  // Keep only the FIRST occurrence
-  const specsMatches = cleaned.match(/<h3>Specifications<\/h3>/gi);
-  if (specsMatches && specsMatches.length > 1) {
-    console.log(`    Found ${specsMatches.length} "Specifications" headers - removing duplicates`);
-
-    // Find the first occurrence
-    const firstIndex = cleaned.indexOf('<h3>Specifications</h3>');
-
-    // Remove all subsequent occurrences along with their content
-    let tempCleaned = cleaned.substring(0, firstIndex);
-    let remaining = cleaned.substring(firstIndex);
-
-    // Keep the first specs section
-    const firstSpecsEnd = remaining.indexOf('</ul>') + 5;
-    tempCleaned += remaining.substring(0, firstSpecsEnd);
-    remaining = remaining.substring(firstSpecsEnd);
-
-    // Remove any subsequent specs sections
-    remaining = remaining.replace(/<h3>Specifications<\/h3>\s*<ul>[\s\S]*?<\/ul>/gi, '');
-
-    cleaned = tempCleaned + remaining;
-    removed = true;
-  }
-
-  // Pattern 2: Remove <h3>Product Information</h3> sections entirely
-  // (these are redundant with Specifications)
-  if (cleaned.includes('<h3>Product Information</h3>') || cleaned.includes('<h4>Product Information</h4>')) {
-    console.log(`    Found "Product Information" section - removing`);
-    cleaned = cleaned.replace(/<h[34]>Product\s+Information<\/h[34]>\s*<ul>[\s\S]*?<\/ul>/gi, '');
-    removed = true;
-  }
-
-  // Pattern 3: Remove standalone spec lines like "Brand: X", "Collection: Y"
-  const redundantPatterns = [
-    /<p>\s*<strong>\s*Brand:\s*<\/strong>[^<]*<\/p>/gi,
-    /<p>\s*<strong>\s*Collection:\s*<\/strong>[^<]*<\/p>/gi,
-    /<p>\s*<strong>\s*Designer:\s*<\/strong>[^<]*<\/p>/gi,
-    /<p>\s*<strong>\s*Type:\s*<\/strong>[^<]*<\/p>/gi,
-    /<p>\s*<strong>\s*Product\s+Type:\s*<\/strong>[^<]*<\/p>/gi,
-  ];
-
-  for (const pattern of redundantPatterns) {
-    if (pattern.test(cleaned)) {
-      cleaned = cleaned.replace(pattern, '');
-      removed = true;
-    }
-  }
-
-  // Clean up extra whitespace
-  cleaned = cleaned.replace(/\n\s*\n\s*\n/g, '\n\n').trim();
-
-  return { cleaned, removed };
-}
-
-async function updateProductDescription(productId: string, newBodyHtml: string): Promise<void> {
-  const url = `https://${SHOPIFY_STORE}/admin/api/${SHOPIFY_API_VERSION}/products/${productId}.json`;
-
-  const response = await fetch(url, {
-    method: 'PUT',
-    headers: {
-      'X-Shopify-Access-Token': SHOPIFY_TOKEN,
-      'Content-Type': 'application/json',
-    },
-    body: JSON.stringify({
-      product: {
-        id: productId,
-        body_html: newBodyHtml,
-      },
-    }),
-  });
-
-  if (!response.ok) {
-    const errorText = await response.text();
-    throw new Error(`Failed to update product ${productId}: ${response.statusText} - ${errorText}`);
-  }
-}
-
-async function main() {
-  console.log('🧹 DWWC Description Cleanup - Remove Duplicate Specifications\n');
-  console.log('='.repeat(80) + '\n');
-
-  const products = await getAllDWWCProducts();
-
-  let processedCount = 0;
-  let updatedCount = 0;
-  let errorCount = 0;
-
-  for (const product of products) {
-    processedCount++;
-
-    console.log(`[${processedCount}/${products.length}] ${product.title}`);
-
-    if (!product.body_html) {
-      console.log('  ⏭️  Skipping - no description\n');
-      continue;
-    }
-
-    const { cleaned, removed } = cleanDuplicateSpecs(product.body_html);
-
-    if (!removed) {
-      console.log('  ✓ No duplicates found\n');
-      continue;
-    }
-
-    try {
-      await updateProductDescription(product.id, cleaned);
-      updatedCount++;
-      console.log('  ✅ Updated successfully\n');
-    } catch (error) {
-      errorCount++;
-      console.error(`  ❌ Error: ${error}\n`);
-    }
-
-    // Rate limiting: 2 requests per second
-    await new Promise(resolve => setTimeout(resolve, 500));
-  }
-
-  console.log('='.repeat(80));
-  console.log('\n📊 FINAL SUMMARY:');
-  console.log(`  Total products: ${products.length}`);
-  console.log(`  Updated: ${updatedCount}`);
-  console.log(`  Errors: ${errorCount}`);
-  console.log(`  No changes needed: ${products.length - updatedCount - errorCount}`);
-  console.log('\n✅ Cleanup complete!');
-}
-
-main().catch(console.error);
diff --git a/DW-Programming/ImportNewSkufromURL/update-all-schumacher-products.ts.bak b/DW-Programming/ImportNewSkufromURL/update-all-schumacher-products.ts.bak
deleted file mode 100644
index 0969743f..00000000
--- a/DW-Programming/ImportNewSkufromURL/update-all-schumacher-products.ts.bak
+++ /dev/null
@@ -1,514 +0,0 @@
-/**
- * Update ALL Schumacher products with data and images from official Schumacher website
- */
-
-import * as dotenv from 'dotenv';
-import * as path from 'path';
-
-// Load .env.local file
-dotenv.config({ path: path.join(__dirname, '.env.local') });
-
-interface SchumacherProduct {
-  id: string;
-  title: string;
-  sku: string;
-}
-
-interface SchumacherProductData {
-  sku: string;
-  name: string;
-  color: string;
-  description: string;
-  collection: string;
-  width: string;
-  horizontalRepeat: string;
-  matchType: string;
-  coverage: string;
-  pretrim: string;
-  railroaded: string;
-  motif: string;
-  scale: string;
-  colors: string;
-  careInstructions: string;
-  flameTest: string;
-  countryOfOrigin: string;
-  imageUrls: string[];
-}
-
-async function getAllSchumacherProducts(): Promise<SchumacherProduct[]> {
-  console.log('📦 Fetching all Schumacher products from Shopify...\n');
-
-  const SHOPIFY_STORE = process.env.SHOPIFY_STORE_DOMAIN;
-  const SHOPIFY_API_VERSION = process.env.SHOPIFY_ADMIN_API_VERSION || '2024-07';
-  const SHOPIFY_ACCESS_TOKEN = process.env.SHOPIFY_ADMIN_ACCESS_TOKEN;
-
-  if (!SHOPIFY_STORE || !SHOPIFY_ACCESS_TOKEN) {
-    throw new Error('Missing required environment variables');
-  }
-
-  const GRAPHQL_ENDPOINT = `https://${SHOPIFY_STORE}/admin/api/${SHOPIFY_API_VERSION}/graphql.json`;
-
-  const allProducts: SchumacherProduct[] = [];
-  let hasNextPage = true;
-  let cursor: string | null = null;
-
-  while (hasNextPage) {
-    const query = `
-      query GetSchumacherProducts($cursor: String) {
-        products(first: 50, after: $cursor, query: "vendor:'Schumacher Wallpaper' AND status:active") {
-          edges {
-            node {
-              id
-              title
-              variants(first: 1) {
-                edges {
-                  node {
-                    sku
-                  }
-                }
-              }
-            }
-            cursor
-          }
-          pageInfo {
-            hasNextPage
-          }
-        }
-      }
-    `;
-
-    const response = await fetch(GRAPHQL_ENDPOINT, {
-      method: 'POST',
-      headers: {
-        'X-Shopify-Access-Token': SHOPIFY_ACCESS_TOKEN,
-        'Content-Type': 'application/json',
-      },
-      body: JSON.stringify({ query, variables: { cursor } }),
-    });
-
-    const result: any = await response.json();
-
-    if (result.errors) {
-      throw new Error(`GraphQL errors: ${JSON.stringify(result.errors)}`);
-    }
-
-    const edges = result.data.products.edges;
-    for (const edge of edges) {
-      const sku = edge.node.variants.edges[0]?.node.sku || '';
-      // Extract Schumacher SKU (5-7 digits, remove prefixes/suffixes)
-      const schumacherSku = sku.match(/\d{5,7}/)?.[0] || sku;
-
-      allProducts.push({
-        id: edge.node.id,
-        title: edge.node.title,
-        sku: schumacherSku,
-      });
-    }
-
-    hasNextPage = result.data.products.pageInfo.hasNextPage;
-    cursor = edges.length > 0 ? edges[edges.length - 1].cursor : null;
-
-    console.log(`  Fetched ${allProducts.length} products so far...`);
-  }
-
-  console.log(`\n✅ Total Schumacher products: ${allProducts.length}\n`);
-  return allProducts;
-}
-
-async function scrapeSchumacherProduct(sku: string, retryCount = 0): Promise<SchumacherProductData | null> {
-  console.log(`  🔍 Scraping Schumacher data for ${sku}...`);
-
-  try {
-    // Try to fetch the product page to verify it exists
-    const testUrl = `https://schumacher.com/catalog/products/${sku}?source=DISCOVER`;
-    const testResponse = await fetch(testUrl);
-
-    // Handle 429 with exponential backoff
-    if (testResponse.status === 429 && retryCount < 3) {
-      const waitTime = Math.pow(2, retryCount) * 10000; // 10s, 20s, 40s
-      console.log(`  ⏳ Rate limited, waiting ${waitTime / 1000}s before retry ${retryCount + 1}/3...`);
-      await new Promise(resolve => setTimeout(resolve, waitTime));
-      return scrapeSchumacherProduct(sku, retryCount + 1);
-    }
-
-    if (!testResponse.ok) {
-      console.log(`  ⚠️  Product ${sku} not found on Schumacher.com (${testResponse.status})`);
-      return null;
-    }
-
-    const html = await testResponse.text();
-
-    // Parse key data from HTML
-    const titleMatch = html.match(/<title>([^<]+)<\/title>/);
-    const title = titleMatch ? titleMatch[1].replace(' | Schumacher', '').trim() : '';
-
-    // Split title into name and color (usually "Pattern Name - Color")
-    const [name, color] = title.split(' - ').map(s => s.trim());
-
-    // Extract description
-    const descMatch = html.match(/"description":"([^"]+)"/);
-    const description = descMatch ? descMatch[1] : '';
-
-    // Extract collection
-    const collectionMatch = html.match(/"collection":\{"name":"([^"]+)"/);
-    const collection = collectionMatch ? collectionMatch[1] : '';
-
-    // Extract ALL images from the HTML (find all srcset and src with the SKU)
-    const imageUrls: string[] = [];
-    const imageMatches = html.matchAll(/https:\/\/cdn-webassets\.fschumacher\.com\/catalog\/[^"']+\.(?:webp|jpg)/g);
-
-    for (const match of imageMatches) {
-      const url = match[0];
-      // Prefer larger sizes: hd-webp > md-webp > sm-webp > xs-webp (hd is highest quality)
-      if (url.includes('/hd-webp/') || url.includes('/md-webp/') || url.includes('/sm-webp/') || url.includes('/xs-webp/')) {
-        // Replace with hd-webp for highest quality
-        const highResUrl = url.replace(/(xs|sm|md)-webp/, 'hd-webp');
-        if (!imageUrls.includes(highResUrl)) {
-          imageUrls.push(highResUrl);
-        }
-      }
-    }
-
-    // If no images found, try standard pattern with hd-webp
-    if (imageUrls.length === 0) {
-      imageUrls.push(
-        `https://cdn-webassets.fschumacher.com/catalog/hd-webp/${sku}.webp`,
-        `https://cdn-webassets.fschumacher.com/catalog/hd-webp/${sku}-1.webp`,
-        `https://cdn-webassets.fschumacher.com/catalog/hd-webp/${sku}-2.webp`,
-        `https://cdn-webassets.fschumacher.com/catalog/hd-webp/${sku}-3.webp`,
-      );
-    }
-
-    // Extract specs (simplified - just get what we can from HTML)
-    const widthMatch = html.match(/"width":\{"value":"([^"]+)"/);
-    const width = widthMatch ? widthMatch[1] : '';
-
-    const repeatMatch = html.match(/"horizontal_repeat":\{"value":"([^"]+)"/);
-    const horizontalRepeat = repeatMatch ? repeatMatch[1] : '';
-
-    // Extract match type
-    const matchTypeMatch = html.match(/"match_type":\{"value":"([^"]+)"/);
-    const matchType = matchTypeMatch ? matchTypeMatch[1] : '';
-
-    // Extract coverage
-    const coverageMatch = html.match(/"coverage":\{"value":"([^"]+)"/);
-    const coverage = coverageMatch ? coverageMatch[1] : '';
-
-    // Extract motif
-    const motifMatch = html.match(/"motif":\{"value":"([^"]+)"/);
-    const motif = motifMatch ? motifMatch[1] : '';
-
-    // Extract scale
-    const scaleMatch = html.match(/"scale":\{"value":"([^"]+)"/);
-    const scale = scaleMatch ? scaleMatch[1] : '';
-
-    // Extract care instructions
-    const careMatch = html.match(/"care":\{"value":"([^"]+)"/);
-    const careInstructions = careMatch ? careMatch[1] : '';
-
-    // Extract country of origin
-    const countryMatch = html.match(/"country_of_origin":\{"value":"([^"]+)"/);
-    const countryOfOrigin = countryMatch ? countryMatch[1] : '';
-
-    // Extract pretrim
-    const pretrimMatch = html.match(/"pretrimmed":\{"value":"([^"]+)"/);
-    const pretrim = pretrimMatch ? pretrimMatch[1] : '';
-
-    // Extract railroaded
-    const railroadedMatch = html.match(/"railroaded":\{"value":"([^"]+)"/);
-    const railroaded = railroadedMatch ? railroadedMatch[1] : '';
-
-    // Extract colors from JSON data (e.g., "color":[{"value":"Black"},...])
-    const colorMatches = html.matchAll(/"color":\[([^\]]+)\]/g);
-    const extractedColors: string[] = [];
-    for (const match of colorMatches) {
-      const colorJson = match[1];
-      // Extract all "value" fields from the color array
-      const valueMatches = colorJson.matchAll(/"value":"([^"]+)"/g);
-      for (const valueMatch of valueMatches) {
-        const colorValue = valueMatch[1];
-        if (colorValue && !extractedColors.includes(colorValue)) {
-          extractedColors.push(colorValue);
-        }
-      }
-    }
-    const colors = extractedColors.join(', ');
-
-    console.log(`  ✅ Found: ${name} - ${color} (${imageUrls.length} images, colors: ${colors || 'none'})`);
-
-    return {
-      sku,
-      name: name || title,
-      color: color || '',
-      description: description || `Premium ${title} wallcovering from Schumacher`,
-      collection: collection || '',
-      width: width || '',
-      horizontalRepeat: horizontalRepeat || '',
-      matchType: matchType || '',
-      coverage: coverage || '',
-      pretrim: pretrim || '',
-      railroaded: railroaded || '',
-      motif: motif || '',
-      scale: scale || '',
-      colors: colors,
-      careInstructions: careInstructions || '',
-      flameTest: 'ASTM E84 Class A',
-      countryOfOrigin: countryOfOrigin || '',
-      imageUrls,
-    };
-  } catch (error) {
-    console.log(`  ❌ Error scraping ${sku}: ${error}`);
-    return null;
-  }
-}
-
-async function updateProductWithSchumacherData(
-  productId: string,
-  productTitle: string,
-  schumacherData: SchumacherProductData
-): Promise<boolean> {
-  console.log(`  📝 Updating product description...`);
-
-  const SHOPIFY_STORE = process.env.SHOPIFY_STORE_DOMAIN!;
-  const SHOPIFY_API_VERSION = process.env.SHOPIFY_ADMIN_API_VERSION || '2024-07';
-  const SHOPIFY_ACCESS_TOKEN = process.env.SHOPIFY_ADMIN_ACCESS_TOKEN!;
-  const GRAPHQL_ENDPOINT = `https://${SHOPIFY_STORE}/admin/api/${SHOPIFY_API_VERSION}/graphql.json`;
-
-  // Build description
-  const specs = [];
-  if (schumacherData.name) specs.push(`<li><strong>Pattern:</strong> ${schumacherData.name}</li>`);
-  if (schumacherData.color) specs.push(`<li><strong>Color:</strong> ${schumacherData.color}</li>`);
-  if (schumacherData.collection) specs.push(`<li><strong>Collection:</strong> ${schumacherData.collection}</li>`);
-  specs.push(`<li><strong>SKU:</strong> ${schumacherData.sku}</li>`);
-  if (schumacherData.width) specs.push(`<li><strong>Width:</strong> ${schumacherData.width}</li>`);
-  if (schumacherData.horizontalRepeat) specs.push(`<li><strong>Horizontal Repeat:</strong> ${schumacherData.horizontalRepeat}</li>`);
-  if (schumacherData.matchType) specs.push(`<li><strong>Match:</strong> ${schumacherData.matchType}</li>`);
-  if (schumacherData.coverage) specs.push(`<li><strong>Coverage:</strong> ${schumacherData.coverage}</li>`);
-  if (schumacherData.motif) specs.push(`<li><strong>Motif:</strong> ${schumacherData.motif}</li>`);
-  if (schumacherData.scale) specs.push(`<li><strong>Scale:</strong> ${schumacherData.scale}</li>`);
-  if (schumacherData.pretrim) specs.push(`<li><strong>Pretrimmed:</strong> ${schumacherData.pretrim}</li>`);
-  if (schumacherData.railroaded) specs.push(`<li><strong>Railroaded:</strong> ${schumacherData.railroaded}</li>`);
-  if (schumacherData.careInstructions) specs.push(`<li><strong>Care Instructions:</strong> ${schumacherData.careInstructions}</li>`);
-  // ALWAYS include flame test - extremely important
-  specs.push(`<li><strong>Flame Test:</strong> ${schumacherData.flameTest}</li>`);
-  if (schumacherData.countryOfOrigin) specs.push(`<li><strong>Country of Origin:</strong> ${schumacherData.countryOfOrigin}</li>`);
-
-  const updatedDescription = `
-<h3>Description</h3>
-<p>${schumacherData.description}</p>
-
-<h3>Specifications</h3>
-<ul>
-${specs.join('\n')}
-</ul>
-`.trim();
-
-  // Build new title with actual Schumacher name and color
-  const newTitle = schumacherData.color
-    ? `${schumacherData.name} - ${schumacherData.color} | Schumacher`
-    : `${schumacherData.name} | Schumacher`;
-
-  // Build tags array
-  const tags: string[] = ['Schumacher', 'Wallpaper', 'ASTM E84 Class A'];
-  if (sku) tags.push(sku); // Add SKU to tags
-  if (schumacherData.collection) tags.push(schumacherData.collection);
-  if (schumacherData.name) tags.push(schumacherData.name);
-  if (schumacherData.color) tags.push(schumacherData.color);
-  if (schumacherData.motif) tags.push(schumacherData.motif);
-  // Add individual colors from the colors field
-  if (schumacherData.colors) {
-    const individualColors = schumacherData.colors.split(', ').map(c => c.trim());
-    for (const colorTag of individualColors) {
-      if (colorTag && !tags.includes(colorTag)) {
-        tags.push(colorTag);
-      }
-    }
-  }
-
-  // Update description, title, AND tags
-  const updateMutation = `
-    mutation UpdateProduct($input: ProductInput!) {
-      productUpdate(input: $input) {
-        product {
-          id
-          title
-          tags
-        }
-        userErrors {
-          field
-          message
-        }
-      }
-    }
-  `;
-
-  try {
-    const updateResponse = await fetch(GRAPHQL_ENDPOINT, {
-      method: 'POST',
-      headers: {
-        'X-Shopify-Access-Token': SHOPIFY_ACCESS_TOKEN,
-        'Content-Type': 'application/json',
-      },
-      body: JSON.stringify({
-        query: updateMutation,
-        variables: {
-          input: {
-            id: productId,
-            title: newTitle,
-            descriptionHtml: updatedDescription,
-            tags: tags,
-          },
-        },
-      }),
-    });
-
-    const updateResult: any = await updateResponse.json();
-
-    if (updateResult.errors || updateResult.data?.productUpdate?.userErrors?.length > 0) {
-      console.log(`  ⚠️  Update error: ${JSON.stringify(updateResult.errors || updateResult.data.productUpdate.userErrors)}`);
-      return false;
-    }
-
-    console.log(`  ✅ Title updated: ${newTitle}`);
-    console.log(`  ✅ Description updated`);
-    console.log(`  ✅ Tags updated: ${tags.join(', ')}`);
-
-    // Upload images
-    console.log(`  📸 Uploading images...`);
-    let uploadedCount = 0;
-
-    for (const imageUrl of schumacherData.imageUrls) {
-      // Check if image exists first
-      const imageCheck = await fetch(imageUrl, { method: 'HEAD' });
-      if (!imageCheck.ok) {
-        console.log(`     ⏭️  Skipping ${imageUrl.split('/').pop()} (404)`);
-        continue;
-      }
-      console.log(`     ⬇️  ${imageUrl.split('/').pop()}`);
-
-      const imageMutation = `
-        mutation productCreateMedia($media: [CreateMediaInput!]!, $productId: ID!) {
-          productCreateMedia(media: $media, productId: $productId) {
-            media {
-              alt
-              status
-            }
-            mediaUserErrors {
-              field
-              message
-            }
-          }
-        }
-      `;
-
-      const imageResponse = await fetch(GRAPHQL_ENDPOINT, {
-        method: 'POST',
-        headers: {
-          'X-Shopify-Access-Token': SHOPIFY_ACCESS_TOKEN,
-          'Content-Type': 'application/json',
-        },
-        body: JSON.stringify({
-          query: imageMutation,
-          variables: {
-            productId: productId,
-            media: [
-              {
-                alt: `${schumacherData.name} - ${schumacherData.color}`,
-                mediaContentType: 'IMAGE',
-                originalSource: imageUrl,
-              },
-            ],
-          },
-        }),
-      });
-
-      const imageResult: any = await imageResponse.json();
-
-      if (!imageResult.errors && imageResult.data?.productCreateMedia?.mediaUserErrors?.length === 0) {
-        uploadedCount++;
-      }
-
-      // Rate limit
-      await new Promise(resolve => setTimeout(resolve, 500));
-    }
-
-    console.log(`  ✅ Uploaded ${uploadedCount} images`);
-    return true;
-  } catch (error) {
-    console.log(`  ❌ Error: ${error}`);
-    return false;
-  }
-}
-
-async function main() {
-  console.log('🎨 Schumacher Bulk Product Updater\n');
-  console.log('='.repeat(80) + '\n');
-
-  try {
-    // Step 1: Get all Schumacher products
-    const products = await getAllSchumacherProducts();
-
-    if (products.length === 0) {
-      console.log('No Schumacher products found.');
-      return;
-    }
-
-    let processedCount = 0;
-    let updatedCount = 0;
-    let skippedCount = 0;
-    let errorCount = 0;
-
-    // Step 2: Process each product
-    for (const product of products) {
-      processedCount++;
-      console.log(`\n[${ processedCount}/${products.length}] ${product.title}`);
-      console.log(`  SKU: ${product.sku}`);
-
-      // Skip if SKU doesn't look like a Schumacher SKU (5-7 digits)
-      if (!/^\d{5,7}$/.test(product.sku)) {
-        console.log(`  ⏭️  Skipping - invalid Schumacher SKU format (need 5-7 digits, got: ${product.sku})`);
-        skippedCount++;
-        continue;
-      }
-
-      // Step 3: Scrape Schumacher data
-      const schumacherData = await scrapeSchumacherProduct(product.sku);
-
-      if (!schumacherData) {
-        console.log(`  ⏭️  Skipping - no data from Schumacher`);
-        skippedCount++;
-        continue;
-      }
-
-      // Step 4: Update product
-      const success = await updateProductWithSchumacherData(
-        product.id,
-        product.title,
-        schumacherData
-      );
-
-      if (success) {
-        updatedCount++;
-      } else {
-        errorCount++;
-      }
-
-      // Rate limiting between products (10 seconds to avoid 429 errors)
-      await new Promise(resolve => setTimeout(resolve, 10000));
-    }
-
-    console.log('\n' + '='.repeat(80));
-    console.log('\n📊 FINAL SUMMARY:');
-    console.log(`  Total products: ${products.length}`);
-    console.log(`  Updated: ${updatedCount}`);
-    console.log(`  Skipped: ${skippedCount}`);
-    console.log(`  Errors: ${errorCount}`);
-    console.log('\n✅ Bulk update complete!');
-  } catch (error) {
-    console.error('\n' + '='.repeat(80));
-    console.error('❌ Fatal error:', error);
-    process.exit(1);
-  }
-}
-
-main();
diff --git a/DW-Programming/room-setting-app/app/api/auth/login-password/route.ts b/DW-Programming/room-setting-app/app/api/auth/login-password/route.ts
index 8163bfc2..a6486029 100644
--- a/DW-Programming/room-setting-app/app/api/auth/login-password/route.ts
+++ b/DW-Programming/room-setting-app/app/api/auth/login-password/route.ts
@@ -4,9 +4,27 @@ import bcrypt from 'bcryptjs';
 import * as fs from 'fs';
 import * as path from 'path';
 
-const AUTH_USERNAME = process.env.AUTH_USERNAME || 'admin';
-// Hardcoded hash for DW2025Secure! (bcrypt hash with special chars that env vars can't handle)
-const AUTH_PASSWORD_HASH = process.env.AUTH_PASSWORD_HASH || '$2b$10$GXL6hj.1OAdCJ7tDSPYIPuhJxq/MgIYynNyAFdqfrwG9T4yImb3H.';
+// Credentials must be supplied via DW_BASIC_AUTH ("user:password") OR an explicit
+// AUTH_PASSWORD_HASH (+ optional AUTH_USERNAME). Fail-closed at module load if
+// neither is set — never default to a hardcoded literal/hash.
+const DW_BASIC_AUTH = process.env.DW_BASIC_AUTH;
+const AUTH_PASSWORD_HASH = process.env.AUTH_PASSWORD_HASH;
+
+let AUTH_USERNAME: string;
+let AUTH_PASSWORD_PLAINTEXT: string | null = null;
+
+if (DW_BASIC_AUTH) {
+  const colonIdx = DW_BASIC_AUTH.indexOf(':');
+  if (colonIdx <= 0 || colonIdx === DW_BASIC_AUTH.length - 1) {
+    throw new Error('DW_BASIC_AUTH must be in "user:password" form');
+  }
+  AUTH_USERNAME = DW_BASIC_AUTH.slice(0, colonIdx);
+  AUTH_PASSWORD_PLAINTEXT = DW_BASIC_AUTH.slice(colonIdx + 1);
+} else if (AUTH_PASSWORD_HASH) {
+  AUTH_USERNAME = process.env.AUTH_USERNAME || 'admin';
+} else {
+  throw new Error('DW_BASIC_AUTH env var required for auth — set in .env before boot');
+}
 
 // Login tracking log file
 const LOGIN_LOG_FILE = '/root/Projects/Designer-Wallcoverings/DW-Programming/room-setting-app/data/login-log.json';
@@ -72,19 +90,27 @@ export async function POST(request: NextRequest) {
       );
     }
 
-    // Verify password hash
-    if (!AUTH_PASSWORD_HASH) {
-      console.error('AUTH_PASSWORD_HASH not configured');
+    // Verify password — DW_BASIC_AUTH plaintext takes priority, AUTH_PASSWORD_HASH is fallback.
+    let isValid = false;
+    if (AUTH_PASSWORD_PLAINTEXT !== null) {
+      console.log('Attempting login (DW_BASIC_AUTH plaintext path):', { username });
+      // Constant-time compare to avoid timing side-channel on the plaintext path.
+      const supplied = Buffer.from(password);
+      const expected = Buffer.from(AUTH_PASSWORD_PLAINTEXT);
+      isValid = supplied.length === expected.length &&
+        require('crypto').timingSafeEqual(supplied, expected);
+    } else if (AUTH_PASSWORD_HASH) {
+      console.log('Attempting login (AUTH_PASSWORD_HASH path):', { username, hashLength: AUTH_PASSWORD_HASH.length });
+      isValid = await bcrypt.compare(password, AUTH_PASSWORD_HASH);
+    } else {
+      // Should be unreachable — module-load guard ensures one of the two is set.
+      console.error('No auth credential source configured');
       return NextResponse.json(
         { success: false, error: 'Authentication not configured' },
         { status: 500 }
       );
     }
 
-    // Compare password with hash
-    console.log('Attempting login:', { username, hashLength: AUTH_PASSWORD_HASH?.length });
-    const isValid = await bcrypt.compare(password, AUTH_PASSWORD_HASH);
-
     if (!isValid) {
       console.log('Password comparison failed');
       logLogin({ timestamp: new Date().toISOString(), username, ip, userAgent, success: false });
diff --git a/DW-Websites/.gitignore b/DW-Websites/.gitignore
index 7e6a9c3e..2c0d4829 100644
--- a/DW-Websites/.gitignore
+++ b/DW-Websites/.gitignore
@@ -10,3 +10,44 @@ dist/
 build/
 .next/
 *.bak
+*.bak-*
+
+# DTD verdict 2026-05-29 — sweep of 519-file dirty state.
+# Each site below is an independent project (own .gitignore / .env /
+# package.json; LosAngelesFabrics has its own .git/). They should be
+# gitified per-project, NOT bundled into the DW-Websites parent monorepo.
+# The parent repo tracks only shared assets (_shared/, prod-mirrored
+# theme templates) + repo-level docs.
+Novasuede/
+GrassclothWallcoverings/
+GrassclothWallpaper.com/
+flockedwallpaper/
+flockedwallpaper-mobile/
+glassbeadedwallpaper/
+GoldLeafWallpaper/
+LosAngelesFabrics/
+Mobile/
+
+# Scratch / experimental theme template variants.
+DesignerWallcoverings-product-CLEAN.liquid
+product.liquid
+product-WORKING.liquid
+product-FIXED.liquid
+product-OPTIMIZED.liquid
+product-FINAL-CLEAN.liquid
+_prod-mirror-product-template.liquid
+
+# Local snapshots + reports (point-in-time, not source of truth).
+backups/
+STATUS_REPORT_*.md
+
+# Operational guardian scripts (per-host cron content, not portable code).
+*-guardian.sh
+dw-guardian-template.sh
+monitor-sites.sh
+site-watchdog.sh
+steve-hourly-report.sh
+novasuede-guardian.sh
+multi-method-test.sh
+quick-headless-test.js
+test-all-sites-headless.js
diff --git a/DW-Websites/GoldLeafWallpaper/index.html b/DW-Websites/GoldLeafWallpaper/index.html
index 65d6ca70..c3dd609b 100644
--- a/DW-Websites/GoldLeafWallpaper/index.html
+++ b/DW-Websites/GoldLeafWallpaper/index.html
@@ -70,8 +70,19 @@
   .search input::placeholder{color:var(--muted)}
   .search svg{width:16px;height:16px;stroke:var(--muted);fill:none;stroke-width:1.5}
 
+  /* Sort + density controls (hard-rule: every product grid) */
+  .controls{display:flex;gap:18px;align-items:center;margin-bottom:24px;flex-wrap:wrap}
+  .controls label,.controls .dens-lbl{font-size:10px;letter-spacing:0.22em;text-transform:uppercase;font-weight:700;color:var(--muted)}
+  .controls select{background:transparent;color:var(--paper);border:1px solid var(--line);font-family:inherit;font-size:11px;letter-spacing:0.10em;text-transform:uppercase;padding:6px 10px;margin-left:8px;cursor:pointer;outline:none}
+  .controls select option{background:var(--bg);color:var(--paper)}
+  .ctl-density{display:flex;align-items:center;gap:12px;margin-left:auto}
+  .ctl-density input[type=range]{-webkit-appearance:none;appearance:none;width:160px;height:1px;background:var(--line);outline:none}
+  .ctl-density input[type=range]::-webkit-slider-thumb{-webkit-appearance:none;appearance:none;width:13px;height:13px;background:var(--gold);cursor:pointer;border-radius:50%}
+  .ctl-density input[type=range]::-moz-range-thumb{width:13px;height:13px;background:var(--gold);cursor:pointer;border-radius:50%;border:0}
+  .dens-ct{font-size:11px;color:var(--muted);letter-spacing:0.18em;text-transform:uppercase;font-weight:600;min-width:56px}
+
   /* Pattern grid */
-  .grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:0}
+  .grid{display:grid;grid-template-columns:repeat(var(--cols,5),1fr);gap:0}
   .item{position:relative;aspect-ratio:1/1.15;cursor:pointer;overflow:hidden;border:1px solid rgba(255,255,255,0.06);transition:transform 0.4s cubic-bezier(0.2,0.8,0.2,1)}
   .item:hover{transform:scale(1.04);z-index:5}
   /* Crop the "PHILLIPE ROMANO / WALLCOVER · FABRIC · LOS ANGELES" watermark band
@@ -129,6 +140,8 @@
     .footer-grid{grid-template-columns:1fr;gap:32px}
     .grid{grid-template-columns:repeat(2,1fr)}
     .search{margin-left:0;flex:1 1 100%}
+    .ctl-density{margin-left:0}
+    .ctl-density input[type=range]{width:120px}
   }
 </style>
 </head>
@@ -173,6 +186,22 @@
     </div>
   </div>
 
+  <div class="controls">
+    <label>Sort
+      <select id="sortSelect" aria-label="Sort patterns">
+        <option value="newest">Newest</option>
+        <option value="color">Color</option>
+        <option value="sku">SKU A–Z</option>
+        <option value="title">Title A–Z</option>
+      </select>
+    </label>
+    <div class="ctl-density">
+      <span class="dens-lbl">Density</span>
+      <input type="range" id="densitySlider" min="3" max="8" step="1" value="5" aria-label="Grid columns">
+      <span class="dens-ct" id="densityLabel">5 cols</span>
+    </div>
+  </div>
+
   <div class="grid" id="grid"></div>
 </section>
 
@@ -252,6 +281,7 @@
   let products = [];
   let currentFilter = 'all';
   let currentSearch = '';
+  let currentSort = 'newest';
 
   function deriveDisplay(p) {
     // Pull color tags (Gold, Silver, Black, etc.) for display
@@ -300,7 +330,25 @@
       }
       return true;
     });
-    render(filtered);
+    render(sortProducts(filtered));
+  }
+
+  function sortProducts(arr){
+    const a = arr.slice();
+    switch(currentSort){
+      case 'color':
+        return a.sort((x,y)=>{
+          const cx=(deriveDisplay(x).colorTags[0]||'zzz').toLowerCase();
+          const cy=(deriveDisplay(y).colorTags[0]||'zzz').toLowerCase();
+          return cx.localeCompare(cy) || String(x.code).localeCompare(String(y.code), undefined, {numeric:true});
+        });
+      case 'sku':
+        return a.sort((x,y)=>String(x.code).localeCompare(String(y.code), undefined, {numeric:true}));
+      case 'title':
+        return a.sort((x,y)=>String(x.title||'').localeCompare(String(y.title||'')));
+      default: // newest — server's natural order
+        return a;
+    }
   }
 
   document.querySelectorAll('.chip').forEach(chip => {
@@ -316,6 +364,35 @@
     applyFilter();
   });
 
+  // Sort control (localStorage-persisted)
+  const sortSel = document.getElementById('sortSelect');
+  if(sortSel){
+    try { currentSort = localStorage.getItem('gl_sort') || 'newest'; } catch(e){}
+    sortSel.value = currentSort;
+    sortSel.addEventListener('change', () => {
+      currentSort = sortSel.value;
+      try { localStorage.setItem('gl_sort', currentSort); } catch(e){}
+      applyFilter();
+    });
+  }
+
+  // Density slider — drives --cols on the grid (localStorage-persisted)
+  const densSlider = document.getElementById('densitySlider');
+  const densLabel = document.getElementById('densityLabel');
+  function setDensity(n){
+    const g = document.getElementById('grid');
+    if(g) g.style.setProperty('--cols', n);
+    if(densLabel) densLabel.textContent = n + ' cols';
+    try { localStorage.setItem('gl_density', n); } catch(e){}
+  }
+  if(densSlider){
+    let savedD = 5;
+    try { savedD = parseInt(localStorage.getItem('gl_density') || '5', 10) || 5; } catch(e){}
+    densSlider.value = savedD;
+    setDensity(savedD);
+    densSlider.addEventListener('input', () => setDensity(parseInt(densSlider.value, 10)));
+  }
+
   // Modal
   function openSampleModal(name, code, img) {
     const modal = document.getElementById('sampleModal');
diff --git a/DW-Websites/GrassclothWallcoverings/.gitignore b/DW-Websites/GrassclothWallcoverings/.gitignore
index c1b218b8..6f524b54 100644
--- a/DW-Websites/GrassclothWallcoverings/.gitignore
+++ b/DW-Websites/GrassclothWallcoverings/.gitignore
@@ -1,8 +1,16 @@
+# --- canonical .gitignore (Steve standing rule, gitify sweep 2026-05-30) ---
 node_modules/
 .env
+.env*
+.env.local
+dist/
+build/
+.next/
 *.log
 .DS_Store
-server.log
-keep-alive.log
+tmp/
+*.bak
+*.bak-*
 .claude/
 .vercel
+.vercel/
diff --git a/DW-Websites/GrassclothWallcoverings/index.html b/DW-Websites/GrassclothWallcoverings/index.html
index 181de8b1..51a9387e 100644
--- a/DW-Websites/GrassclothWallcoverings/index.html
+++ b/DW-Websites/GrassclothWallcoverings/index.html
@@ -98,6 +98,7 @@
     </script>
 
     <link rel="stylesheet" href="styles.css">
+    <script src="/corner-nav.js" defer></script>
 </head>
 <body>
     <!-- Header -->
diff --git a/DW-Websites/GrassclothWallcoverings/products-shopify-v4.js b/DW-Websites/GrassclothWallcoverings/products-shopify-v4.js
index 25e36230..a12d7cd7 100644
--- a/DW-Websites/GrassclothWallcoverings/products-shopify-v4.js
+++ b/DW-Websites/GrassclothWallcoverings/products-shopify-v4.js
@@ -136,7 +136,33 @@ function createNewModal() {
 
 // Global products array for sorting
 let allProducts = [];
-let currentSortOrder = 'name-asc';
+let currentSortOrder = localStorage.getItem('gcw_sort') || 'newest';
+
+// --- Sort helpers (HARD RULE: every product grid needs sort + density) ---
+// Color buckets: group similar hues by keyword in the resolved color name.
+const COLOR_BUCKETS = [
+    { name: 'White / Cream', rx: /(white|ivory|cream|linen|pearl|oyster|parchment|champagne|opal|cloud|dove|fog|mist|platinum|silver|blush|peach)/i },
+    { name: 'Neutral / Tan', rx: /(natural|tan|sand|wheat|beige|taupe|khaki|stone|camel|honey|butterscotch|driftwood|oyster|smoke|ash|graphite)/i },
+    { name: 'Brown', rx: /(brown|chocolate|espresso|mocha|cocoa|walnut|chestnut|mahogany|hickory|cedar|tobacco|cinnamon|sienna|auburn|ginger|rust|copper|bronze|amber|terracotta|coral|caramel)/i },
+    { name: 'Gray / Black', rx: /(gray|grey|charcoal|pewter|slate|carbon|ebony|onyx|shadow|graphite|smoke|carbon)/i },
+    { name: 'Green', rx: /(green|sage|olive|moss|jade|mint|seafoam|forest|teal)/i },
+    { name: 'Blue', rx: /(blue|navy|sky|midnight)/i },
+    { name: 'Purple / Pink', rx: /(plum|mauve|burgundy|blush|coral|peach)/i },
+    { name: 'Gold / Yellow', rx: /(gold|amber|honey|butterscotch)/i }
+];
+function colorBucket(name) {
+    for (let i = 0; i < COLOR_BUCKETS.length; i++) {
+        if (COLOR_BUCKETS[i].rx.test(name)) return i;
+    }
+    return COLOR_BUCKETS.length; // unmatched goes last
+}
+// Style buckets: coarse texture/style grouping for grasscloth color names.
+function styleBucket(name) {
+    if (/(white|ivory|cream|linen|pearl|natural|tan|sand|wheat|beige|champagne|oyster|parchment)/i.test(name)) return 0; // Light / Neutral
+    if (/(gray|grey|charcoal|pewter|slate|silver|platinum|graphite|smoke|ash|fog|mist|dove|cloud|shadow|carbon|ebony|onyx)/i.test(name)) return 1; // Cool / Modern
+    if (/(brown|chocolate|espresso|mocha|walnut|chestnut|mahogany|hickory|cedar|tobacco|cinnamon|sienna|auburn|rust|copper|bronze|amber|terracotta)/i.test(name)) return 2; // Warm / Earthy
+    return 3; // Accent / Bold
+}
 
 // Function to render products
 function renderProducts(products, colorMap) {
@@ -191,67 +217,94 @@ function renderProducts(products, colorMap) {
     console.log(`Displayed ${products.length} Hollywood Abaca products`);
 }
 
-// Function to sort products
+// Function to sort products. "Newest" = server's natural array order.
 function sortProducts(sortOrder, colorMap) {
     const sortedProducts = [...allProducts];
+    const nameOf = (p) => colorMap[p.sku] || 'Natural';
+    const skuOf = (p) => p.sku || p.handle || '';
 
-    switch(sortOrder) {
-        case 'name-asc':
+    switch (sortOrder) {
+        case 'color':
             sortedProducts.sort((a, b) => {
-                const nameA = colorMap[a.sku] || 'Natural';
-                const nameB = colorMap[b.sku] || 'Natural';
-                return nameA.localeCompare(nameB);
+                const ba = colorBucket(nameOf(a)), bb = colorBucket(nameOf(b));
+                return ba - bb || nameOf(a).localeCompare(nameOf(b));
             });
             break;
-        case 'price-low':
+        case 'style':
             sortedProducts.sort((a, b) => {
-                return parseFloat(a.price) - parseFloat(b.price);
+                const sa = styleBucket(nameOf(a)), sb = styleBucket(nameOf(b));
+                return sa - sb || nameOf(a).localeCompare(nameOf(b));
             });
             break;
-        case 'price-high':
-            sortedProducts.sort((a, b) => {
-                return parseFloat(b.price) - parseFloat(a.price);
-            });
+        case 'sku-asc':
+            sortedProducts.sort((a, b) => skuOf(a).localeCompare(skuOf(b), undefined, { numeric: true }));
+            break;
+        case 'title-asc':
+            sortedProducts.sort((a, b) => nameOf(a).localeCompare(nameOf(b)));
+            break;
+        case 'newest':
+        default:
+            // leave in natural (server) order
             break;
     }
 
     renderProducts(sortedProducts, colorMap);
 }
 
-// Function to create sort dropdown
-function createSortDropdown(colorMap) {
-    const productsSection = document.querySelector('#products .container');
-    if (!productsSection) return;
-
-    const sortContainer = document.createElement('div');
-    sortContainer.style.cssText = 'display: flex; justify-content: flex-end; margin-bottom: 20px; gap: 10px; align-items: center;';
-
-    const sortLabel = document.createElement('label');
-    sortLabel.textContent = 'Sort by:';
-    sortLabel.style.cssText = 'font-weight: 500; color: #333;';
-    sortLabel.setAttribute('for', 'sortDropdown');
-
-    const sortSelect = document.createElement('select');
-    sortSelect.id = 'sortDropdown';
-    sortSelect.style.cssText = 'padding: 8px 12px; border: 1px solid #ddd; border-radius: 4px; background: white; cursor: pointer; font-size: 14px;';
-
-    sortSelect.innerHTML = `
-        <option value="name-asc">Name A-Z</option>
-        <option value="price-low">Price Low-High</option>
-        <option value="price-high">Price High-Low</option>
+// Function to create the sort + density controls bar above the grid.
+// HARD RULE: every DW product grid needs a sort <select> + density slider,
+// both persisted to localStorage.
+function createGridControls(colorMap) {
+    const grid = document.getElementById('productsGrid');
+    if (!grid || !grid.parentNode) return;
+    if (document.getElementById('gridControls')) return; // idempotent
+
+    const bar = document.createElement('div');
+    bar.id = 'gridControls';
+    bar.className = 'grid-controls';
+    bar.innerHTML = `
+        <div class="ctrl">
+            <label for="sortSelect">Sort</label>
+            <select id="sortSelect">
+                <option value="newest">Newest</option>
+                <option value="color">Color</option>
+                <option value="style">Style</option>
+                <option value="sku-asc">SKU A&rarr;Z</option>
+                <option value="title-asc">Title A&rarr;Z</option>
+            </select>
+        </div>
+        <div class="ctrl">
+            <label for="densitySlider">Grid</label>
+            <input type="range" id="densitySlider" min="4" max="10" step="1" value="4">
+            <span class="density-value" id="densityLabel">4 cols</span>
+        </div>
     `;
+    grid.parentNode.insertBefore(bar, grid);
 
+    // --- Sort wire-up ---
+    const sortSelect = document.getElementById('sortSelect');
+    sortSelect.value = currentSortOrder;
     sortSelect.addEventListener('change', (e) => {
         currentSortOrder = e.target.value;
+        try { localStorage.setItem('gcw_sort', currentSortOrder); } catch (err) {}
         sortProducts(currentSortOrder, colorMap);
     });
 
-    sortContainer.appendChild(sortLabel);
-    sortContainer.appendChild(sortSelect);
-
-    const grid = document.getElementById('productsGrid');
-    if (grid && grid.parentNode) {
-        grid.parentNode.insertBefore(sortContainer, grid);
+    // --- Density wire-up ---
+    const slider = document.getElementById('densitySlider');
+    const dlabel = document.getElementById('densityLabel');
+    function setDensity(n) {
+        document.documentElement.style.setProperty('--cols', n);
+        dlabel.textContent = n + ' cols';
+        try { localStorage.setItem('gcw_density', n); } catch (err) {}
+    }
+    slider.addEventListener('input', (e) => setDensity(parseInt(e.target.value, 10)));
+    const savedDensity = parseInt(localStorage.getItem('gcw_density') || '4', 10);
+    if (savedDensity >= 4 && savedDensity <= 10) {
+        slider.value = savedDensity;
+        setDensity(savedDensity);
+    } else {
+        setDensity(4);
     }
 }
 
@@ -358,10 +411,10 @@ window.addEventListener('load', async function() {
         // Store products globally for sorting
         allProducts = data.products;
 
-        // Create sort dropdown
-        createSortDropdown(colorMap);
+        // Create sort + density controls (HARD RULE)
+        createGridControls(colorMap);
 
-        // Display all products with default sorting (Name A-Z)
+        // Display all products using the persisted/default sort
         sortProducts(currentSortOrder, colorMap);
 
     } catch (err) {
diff --git a/DW-Websites/GrassclothWallcoverings/styles.css b/DW-Websites/GrassclothWallcoverings/styles.css
index 96e40225..0ed2dd1c 100644
--- a/DW-Websites/GrassclothWallcoverings/styles.css
+++ b/DW-Websites/GrassclothWallcoverings/styles.css
@@ -14,6 +14,7 @@
     --border-color: #d1d5db;
     --corporate-blue: #003d5b;
     --hospitality-gold: #d4af37;
+    --cols: 4;
 }
 
 body {
@@ -162,11 +163,58 @@ header {
 
 .products-grid {
     display: grid;
-    grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
+    grid-template-columns: repeat(var(--cols), 1fr);
     gap: 2rem;
     margin-top: 3rem;
 }
 
+/* Tablet: clamp the slider so cards never get too small */
+@media (min-width: 769px) and (max-width: 1024px) {
+    .products-grid {
+        grid-template-columns: repeat(min(var(--cols), 4), 1fr);
+    }
+}
+
+/* Sort + density controls above the grid (HARD RULE) */
+.grid-controls {
+    display: flex;
+    flex-wrap: wrap;
+    justify-content: flex-end;
+    align-items: center;
+    gap: 1.25rem;
+    margin: 2rem 0 0;
+}
+.grid-controls .ctrl {
+    display: flex;
+    align-items: center;
+    gap: 0.5rem;
+}
+.grid-controls label {
+    font-size: 0.85rem;
+    font-weight: 500;
+    color: var(--primary-color);
+    letter-spacing: 0.02em;
+}
+.grid-controls select {
+    padding: 8px 12px;
+    border: 1px solid var(--border-color);
+    border-radius: 4px;
+    background: #fff;
+    color: var(--text-color);
+    font-size: 0.85rem;
+    cursor: pointer;
+}
+.grid-controls input[type="range"] {
+    accent-color: var(--secondary-color);
+    cursor: pointer;
+    width: 130px;
+}
+.grid-controls .density-value {
+    font-size: 0.8rem;
+    color: var(--text-color);
+    min-width: 3.5em;
+}
+
 .product-card {
     background: white;
     border: 1px solid var(--border-color);
@@ -747,11 +795,15 @@ footer {
     }
 
     .products-grid {
-        grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
+        grid-template-columns: repeat(min(var(--cols), 2), 1fr);
         gap: 0.75rem;
         padding: 0 0.5rem;
     }
 
+    .grid-controls {
+        justify-content: center;
+    }
+
     .product-card {
         -webkit-tap-highlight-color: transparent;
     }
diff --git a/DW-Websites/GrassclothWallpaper.com/.gitignore b/DW-Websites/GrassclothWallpaper.com/.gitignore
index 581e12c4..eb018aec 100644
--- a/DW-Websites/GrassclothWallpaper.com/.gitignore
+++ b/DW-Websites/GrassclothWallpaper.com/.gitignore
@@ -1,7 +1,18 @@
+# --- canonical .gitignore (Steve standing rule, gitify sweep 2026-05-30) ---
 node_modules/
 .env
+.env*
+.env.local
+dist/
+build/
+.next/
 *.log
 .DS_Store
-server.log
-keep-alive.log
-.claude/
\ No newline at end of file
+tmp/
+*.bak
+*.bak-*
+.claude/
+.vercel
+.vercel/
+# --- large-file excludes (>5MB, gitify sweep) ---
+grasscloth-collection-all.json
diff --git a/DW-Websites/GrassclothWallpaper.com/index.html b/DW-Websites/GrassclothWallpaper.com/index.html
index e2c17467..f9106079 100644
--- a/DW-Websites/GrassclothWallpaper.com/index.html
+++ b/DW-Websites/GrassclothWallpaper.com/index.html
@@ -663,6 +663,78 @@
     <script src="/corner-nav.js" defer></script>
 </head>
 <body>
+<!-- Theme toggle (theme1 / theme2) — query-param-gated preview surface
+     (Steve 2026-05-29). Mirror of dw-domain-fleet shared/render.js productPage()
+     block (commit 34fbe0e). No admin auth → security-by-obscurity: ?theme=2
+     flips to theme2 and persists in localStorage 'wallco-page-theme'. Toggle UI
+     hidden until ?theme=N or localStorage opt-in. Shares the wallco-page-theme
+     localStorage key for cross-site continuity. -->
+<script>
+(function(){
+  var qs = new URLSearchParams(location.search);
+  var fromUrl = qs.get('theme');
+  var saved = null; try { saved = localStorage.getItem('wallco-page-theme'); } catch(e){}
+  var theme = null;
+  if (fromUrl === '2' || fromUrl === 'theme2') theme = 'theme2';
+  else if (fromUrl === '1' || fromUrl === 'theme1') theme = 'theme1';
+  else if (saved === 'theme2' || saved === 'theme1') theme = saved;
+  if (theme) {
+    document.documentElement.setAttribute('data-page-theme', theme);
+    if (fromUrl) { try { localStorage.setItem('wallco-page-theme', theme); } catch(e){} }
+  }
+  /* No opt-in → no attribute set, toggle stays display:none (gate works). */
+})();
+</script>
+<style id="page-theme-css-fleet">
+  [data-page-theme="theme2"] body{
+    --bg:#ffffff !important; --ink:#0a0a0a !important;
+    --muted:#5a5a5a !important; --line:#e5e5e5 !important;
+    --card-bg:#fafafa !important; --accent:#c14a2e !important;
+    background:#fff !important; color:#0a0a0a !important;
+  }
+  [data-page-theme="theme2"] h1,
+  [data-page-theme="theme2"] h2,
+  [data-page-theme="theme2"] h3{ letter-spacing:-0.01em; font-weight:500; }
+  [data-page-theme="theme2"] .card{
+    background:#fafafa !important; border:1px solid #e5e5e5 !important; box-shadow:none !important;
+  }
+  #page-theme-toggle{ display:none; }
+  [data-page-theme] #page-theme-toggle{ display:inline-flex; }
+  #page-theme-toggle{
+    position:fixed; top:96px; right:14px; z-index:90;
+    gap:0; align-items:center;
+    background:rgba(255,255,255,.92); border:1px solid #d8d2c5;
+    border-radius:999px; padding:3px; backdrop-filter:blur(6px);
+    box-shadow:0 2px 10px rgba(0,0,0,.08);
+    font:600 11px ui-sans-serif,system-ui; letter-spacing:.06em; text-transform:uppercase;
+  }
+  #page-theme-toggle button{
+    border:0; background:transparent; color:#5a5048; cursor:pointer;
+    padding:6px 14px; border-radius:999px; font:inherit; transition:all .15s;
+  }
+  #page-theme-toggle button[aria-pressed="true"]{ background:#1a1714; color:#faf7f2; }
+  #page-theme-toggle button:hover:not([aria-pressed="true"]){ color:#1a1714; }
+</style>
+<div id="page-theme-toggle" role="group" aria-label="Page theme">
+  <button type="button" data-theme="theme1" aria-pressed="false">Theme 1</button>
+  <button type="button" data-theme="theme2" aria-pressed="false">Theme 2</button>
+</div>
+<script>
+(function(){
+  var cur = document.documentElement.getAttribute('data-page-theme') || 'theme1';
+  var btns = document.querySelectorAll('#page-theme-toggle button');
+  function sync(t){ btns.forEach(function(b){ b.setAttribute('aria-pressed', b.dataset.theme === t ? 'true' : 'false'); }); }
+  sync(cur);
+  btns.forEach(function(b){
+    b.addEventListener('click', function(){
+      var t = b.dataset.theme;
+      document.documentElement.setAttribute('data-page-theme', t);
+      try { localStorage.setItem('wallco-page-theme', t); } catch(e){}
+      sync(t);
+    });
+  });
+})();
+</script>
     <!-- Header -->
     <header class="header">
         <div class="header-content">
diff --git a/DW-Websites/GrassclothWallpaper.com/server.js b/DW-Websites/GrassclothWallpaper.com/server.js
index 5ac014f4..c78495d5 100644
--- a/DW-Websites/GrassclothWallpaper.com/server.js
+++ b/DW-Websites/GrassclothWallpaper.com/server.js
@@ -1,8 +1,34 @@
 const http = require('http');
+const handleSubstituteRequest = require('./raw-substitute-handler');
 const fs = require('fs');
 const path = require('path');
+require('dotenv').config({ path: '/root/DW-Agents/claudette-agent/.env' });
+const { Pool } = require('pg');
+const GEORGE_PORT = 9850;
+const GEORGE_AUTH = process.env.GEORGE_AUTH ||
+  ('Basic ' + Buffer.from(process.env.GEORGE_BASIC_AUTH || 'admin:I3YusisdESUNdxtrmlb3QJeu9q8ODKJO').toString('base64'));
+
 
 const PORT = process.env.PORT || 8000;
+const pool = new Pool({ connectionString: process.env.DB_URL || process.env.DATABASE_URL });
+
+let productsCache = null;
+let productsCacheAt = 0;
+async function getProducts() {
+    const now = Date.now();
+    if (productsCache && now - productsCacheAt < 60_000) return productsCache;
+    const { rows } = await pool.query(`
+        SELECT sku, name, description, image_url, hex_color, hex2_color,
+               lightness, hue, saturation, color_variation,
+               vendor, product_type, price, source_url, available_for_sale
+        FROM grasscloth_products
+        WHERE image_url IS NOT NULL
+        ORDER BY hue NULLS LAST, lightness NULLS LAST, sku
+    `);
+    productsCache = rows;
+    productsCacheAt = now;
+    return rows;
+}
 
 const mimeTypes = {
     '.html': 'text/html',
@@ -18,11 +44,30 @@ const mimeTypes = {
 };
 
 const server = http.createServer((req, res) => {
+    // Substitute request intake (drop-in handler)
+    if (req.method === "POST" && req.url === "/api/send-substitute-request") {
+        return handleSubstituteRequest(req, res);
+    }
+
     // Add CORS headers for all requests
     res.setHeader('Access-Control-Allow-Origin', '*');
     res.setHeader('Access-Control-Allow-Methods', 'GET, OPTIONS');
     res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
 
+    if (req.url === '/api/products' || req.url.startsWith('/api/products?')) {
+        getProducts()
+            .then(rows => {
+                res.writeHead(200, { 'Content-Type': 'application/json', 'Cache-Control': 'public, max-age=60' });
+                res.end(JSON.stringify(rows));
+            })
+            .catch(err => {
+                console.error('/api/products error:', err.message);
+                res.writeHead(500, { 'Content-Type': 'application/json' });
+                res.end(JSON.stringify({ error: 'db_error', message: err.message }));
+            });
+        return;
+    }
+
     // Handle image requests from /images/ path
     if (req.url.startsWith('/images/')) {
         const imageName = req.url.replace('/images/', '');
@@ -133,7 +178,10 @@ const server = http.createServer((req, res) => {
         }
     }
 
-    let filePath = '.' + req.url;
+    // Strip query string before filesystem lookup so `/?theme=2` resolves
+    // to `./` (then to ./index.html) rather than `./?theme=2` (404).
+    // Steve 2026-05-29 — required for the wallco-page-theme ?theme=N toggle.
+    let filePath = '.' + req.url.split('?')[0];
     if (filePath === './') {
         filePath = './index.html';
     }
diff --git a/DW-Websites/LosAngelesFabrics b/DW-Websites/LosAngelesFabrics
index fecd589b..6119610b 160000
--- a/DW-Websites/LosAngelesFabrics
+++ b/DW-Websites/LosAngelesFabrics
@@ -1 +1 @@
-Subproject commit fecd589bcb9612d6d50b8b775aafd9d287be2d27
+Subproject commit 6119610ba6b615336f72f19670e8594f37f663c4
diff --git a/DW-Websites/Novasuede/.gitignore b/DW-Websites/Novasuede/.gitignore
index 4c49bd78..a73c839f 100644
--- a/DW-Websites/Novasuede/.gitignore
+++ b/DW-Websites/Novasuede/.gitignore
@@ -1 +1,19 @@
+# --- canonical .gitignore (Steve standing rule, gitify sweep 2026-05-30) ---
+node_modules/
 .env
+.env*
+.env.local
+dist/
+build/
+.next/
+*.log
+.DS_Store
+tmp/
+*.bak
+*.bak-*
+.claude/
+.vercel
+.vercel/
+# --- large-file excludes (>5MB, gitify sweep) ---
+images/left-curtain-d1.png
+images/right-curtain-d1.png
diff --git a/DW-Websites/Novasuede/index.html b/DW-Websites/Novasuede/index.html
index b95fcb9d..8033e945 100644
--- a/DW-Websites/Novasuede/index.html
+++ b/DW-Websites/Novasuede/index.html
@@ -2734,5 +2734,6 @@
     <!-- Central sample-request hub. Any element with data-dw-sample triggers the
          hub modal; existing chip-level Sample buttons keep their inline modal. -->
     <script src="https://auth.agentabrams.com/widget/sample.js" data-site="novasuede.com" defer></script>
+    <script src="/corner-nav.js" defer></script>
 </body>
 </html>
\ No newline at end of file
diff --git a/DW-Websites/_prod-mirror/product.liquid.bak-pre-design-coordinate b/DW-Websites/_prod-mirror/product.liquid.bak-pre-design-coordinate
deleted file mode 100644
index 0de7cbf3..00000000
--- a/DW-Websites/_prod-mirror/product.liquid.bak-pre-design-coordinate
+++ /dev/null
@@ -1,355 +0,0 @@
-{% render 'breadcrumbs' %}
-
-{% liquid
-  assign enable_zoom = section.settings.enable_zoom
-  assign enable_cart_redirection = section.settings.enable_cart_redirection
-  assign images_layout = section.settings.images_layout
-  assign enable_video_autoplay = section.settings.enable_video_autoplay
-  assign enable_video_looping = section.settings.enable_video_looping
-  assign enable_linked_options = true
-  assign show_vendor = settings.show_vendor
-  assign show_social_media_icons = section.settings.show_social_media_icons
-  assign show_payment_button = section.settings.show_payment_button
-
-  for block in section.blocks
-    if block.type == 'complementary_products'
-      assign product_recommendation_limit = block.settings.product_recommendation_limit
-      break
-    endif
-  endfor
-%}
-
-{% if images_layout == 'masonry' %}
-  {% comment %}Related products in masonry grid must be below product{% endcomment %}
-  {% assign related_products_position_right = false %}
-{% endif %}
-
-<script
-  type="application/json"
-  data-section-type="product"
-  data-section-id="{{ section.id }}"
-  data-section-data
->
-  {
-    "product": {{ product | json }},
-    "product_settings": {
-      "addToCartText": {{ 'products.product.add_to_cart' | t | json }},
-      "enableHistory": true,
-      "processingText": {{ 'products.product.processing' | t | json }},
-      "setQuantityText": {{ 'products.product.set_quantity' | t | json }},
-      "soldOutText": {{ 'products.product.sold_out' | t | json }},
-      "unavailableText": {{ 'products.product.unavailable' | t | json }}
-    },
-    "images_layout": {{ images_layout | json }},
-    "enable_zoom": {{ enable_zoom | json }},
-    "enable_video_autoplay": {{ enable_video_autoplay | json }},
-    "enable_video_looping": {{ enable_video_looping | json }},
-    "enable_cart_redirection": {{ enable_cart_redirection | json }},
-    "enable_fixed_positioning": true,
-    "product_recommendation_limit": {{ product_recommendation_limit | json }}
-  }
-</script>
-
-{% render 'product-success-labels' %}
-<section class="product-container">
-  {%
-    render 'product',
-    product: product,
-    enable_zoom: enable_zoom,
-    images_layout: images_layout,
-    enable_linked_options: enable_linked_options,
-    show_vendor: show_vendor,
-    show_social_media_icons: show_social_media_icons,
-    show_payment_button: show_payment_button,
-  %}
-
-  <div
-    class="product-recommendations-wrapper--right"
-    data-product-recommendations-right
-  >
-  </div>
-</section>
-
-{% schema %}
-{
-  "name": "Product pages",
-  "settings": [
-    {
-      "type": "checkbox",
-      "id": "enable_cart_redirection",
-      "label": "Enable cart redirection",
-      "info": "Automatically sends users to the Cart page after adding a product.",
-      "default": false
-    },
-    {
-      "type": "checkbox",
-      "id": "show_payment_button",
-      "label": "Show dynamic checkout button",
-      "info": "Each customer will see their preferred payment method from those available on your store, such as PayPal or Apple Pay. [Learn more](https://help.shopify.com/manual/using-themes/change-the-layout/dynamic-checkout)",
-      "default": true
-    },
-    {
-      "type": "header",
-      "content": "Media"
-    },
-    {
-      "type": "paragraph",
-      "content": "Learn more about [media types](https://help.shopify.com/en/manual/products/product-media/product-media-types)"
-    },
-    {
-      "type": "select",
-      "id": "images_layout",
-      "label": "Layout",
-      "options": [
-        {
-          "label": "Slideshow",
-          "value": "slideshow"
-        },
-        {
-          "label": "List",
-          "value": "list"
-        },
-        {
-          "label": "Masonry",
-          "value": "masonry"
-        }
-      ],
-      "default": "slideshow"
-    },
-    {
-      "type": "checkbox",
-      "id": "enable_zoom",
-      "label": "Enable image zoom",
-      "info": "Zoom only works with the slideshow image layout"
-    },
-    {
-      "type": "checkbox",
-      "id": "enable_video_autoplay",
-      "label": "Enable video autoplay",
-      "default": true
-    },
-    {
-      "type": "checkbox",
-      "id": "enable_video_looping",
-      "label": "Enable video looping"
-    }
-  ],
-  "blocks": [
-    {
-      "type": "@app"
-    },
-    {
-      "type": "collapsible-tab",
-      "name": "Collapsible tab",
-      "settings": [
-        {
-          "type": "text",
-          "id": "collapsible_tab_heading",
-          "label": "Heading",
-          "default": "Collapsible tab"
-        },
-        {
-          "type": "richtext",
-          "id": "collapsible_tab_text",
-          "label": "Text",
-          "default": "<p>Use this text to share information about your product.</p>"
-        }
-      ]
-    },
-    {
-      "type": "custom-liquid",
-      "name": "Custom liquid",
-      "settings": [
-        {
-          "type": "liquid",
-          "id": "custom_liquid",
-          "label": "Custom liquid",
-          "info": "Add app snippets or other Liquid code to create advanced customizations."
-        }
-      ]
-    },
-    {
-      "type": "tabs",
-      "name": "Tabs",
-      "limit": 1,
-      "settings": [
-        {
-          "type": "checkbox",
-          "id": "show_product_description",
-          "label": "Show product description",
-          "default": false
-        },
-        {
-          "type": "header",
-          "content": "Tab"
-        },
-        {
-          "type": "text",
-          "id": "tab_heading_1",
-          "label": "Heading",
-          "default": "Tab 1"
-        },
-        {
-          "type": "richtext",
-          "id": "tab_text_1",
-          "label": "Text",
-          "default": "<p>Tab 1 content goes here.</p>"
-        },
-        {
-          "type": "header",
-          "content": "Tab"
-        },
-        {
-          "type": "text",
-          "id": "tab_heading_2",
-          "label": "Heading",
-          "default": "Tab 2"
-        },
-        {
-          "type": "richtext",
-          "id": "tab_text_2",
-          "label": "Text",
-          "default": "<p>Tab 2 content goes here.</p>"
-        },
-        {
-          "type": "header",
-          "content": "Tab"
-        },
-        {
-          "type": "text",
-          "id": "tab_heading_3",
-          "label": "Heading",
-          "default": "Tab 3"
-        },
-        {
-          "type": "richtext",
-          "id": "tab_text_3",
-          "label": "Text",
-          "default": "<p>Tab 3 content goes here.</p>"
-        }
-      ]
-    },
-    {
-      "type": "title",
-      "name": "Title",
-      "limit": 1
-    },
-    {
-      "type": "vendor",
-      "name": "Vendor",
-      "limit": 1
-    },
-    {
-      "type": "social",
-      "name": "Social",
-      "limit": 1
-    },
-    {
-      "type": "description",
-      "name": "Description",
-      "limit": 1
-    },
-    {
-      "type": "price",
-      "name": "Price",
-      "limit": 1
-    },
-    {
-      "type": "form",
-      "name": "Form",
-      "limit": 1,
-      "settings": [
-        {
-          "type": "paragraph",
-          "content": "Customize form features for the product in the products portion of the theme settings."
-        },
-        {
-          "type": "checkbox",
-          "id": "show_gift_card_recipient_form",
-          "label": "t:sections.product.blocks.form.show_gift_card_recipient_form.label",
-          "info": "t:sections.product.blocks.form.show_gift_card_recipient_form.info",
-          "default": false
-        }
-      ]
-    },
-    {
-      "type": "rating",
-      "name": "Product rating",
-      "limit": 1,
-      "settings": [
-        {
-          "type": "paragraph",
-          "content": "To display a rating, add a product rating app. [Learn more](https://apps.shopify.com/product-reviews)"
-        }
-      ]
-    },
-    {
-      "type": "complementary_products",
-      "name": "Complementary products",
-      "limit": 1,
-      "settings": [
-        {
-          "type": "paragraph",
-          "content": "To select complementary products, add the Search & Discovery app. [Learn more](https:\/\/shopify.dev\/themes\/product-merchandising\/recommendations)"
-        },
-        {
-          "type": "text",
-          "id": "heading",
-          "label": "Heading",
-          "default": "Pairs well with"
-        },
-        {
-          "type": "range",
-          "id": "product_recommendation_limit",
-          "label": "Maximum products to show",
-          "min": 1,
-          "max": 10,
-          "default": 5
-        },
-        {
-          "type": "range",
-          "id": "products_per_slide",
-          "label": "Number of products per page",
-          "min": 1,
-          "max": 3,
-          "default": 2
-        }
-      ]
-    }
-  ]
-}
-
-{% endschema %}
-
-<div class="keywords-desktop" style="padding-left: 0ch; max-width: 80ch; line-height: 1.6;">
-  <h4 style="padding-left: 3ch; margin-bottom: 0;">KEYWORDS:</h4>
-  <div style="padding-left: 3ch;">
-    {% assign line = "" %}
-    {% assign char_limit = 96 %}
-    {% assign separator = ", " %}
-    {% assign plain_line = "" %}
-    {% for tag in product.tags %}
-      {% unless tag == "display_variant" %}
-        {% assign tag_link = '<a href="/search?q=' | append: tag | append: '">' | append: tag | append: '</a>' %}
-        {% assign tag_text = tag | append: separator %}
-        {% assign test_line = plain_line | append: tag_text %}
-        {% assign test_size = test_line | size %}
-        {% if test_size > char_limit %}
-          {{ line }}<br>
-          {% assign line = tag_link | append: separator %}
-          {% assign plain_line = tag_text %}
-        {% else %}
-          {% assign line = line | append: tag_link | append: separator %}
-          {% assign plain_line = test_line %}
-        {% endif %}
-      {% endunless %}
-    {% endfor %}
-    {{ line }}
-  </div>
-</div>
-
-{% comment %} Recently Viewed Products — tracks current product + shows carousel {% endcomment %}
-{% if settings.dw_enable_recently_viewed %}
-
-  {% render 'recently-viewed' %}
-{% endif %}
diff --git a/DW-Websites/flockedwallpaper/.gitignore b/DW-Websites/flockedwallpaper/.gitignore
index 4c49bd78..6f524b54 100644
--- a/DW-Websites/flockedwallpaper/.gitignore
+++ b/DW-Websites/flockedwallpaper/.gitignore
@@ -1 +1,16 @@
+# --- canonical .gitignore (Steve standing rule, gitify sweep 2026-05-30) ---
+node_modules/
 .env
+.env*
+.env.local
+dist/
+build/
+.next/
+*.log
+.DS_Store
+tmp/
+*.bak
+*.bak-*
+.claude/
+.vercel
+.vercel/
diff --git a/DW-Websites/glassbeadedwallpaper/.gitignore b/DW-Websites/glassbeadedwallpaper/.gitignore
index 4c49bd78..6f524b54 100644
--- a/DW-Websites/glassbeadedwallpaper/.gitignore
+++ b/DW-Websites/glassbeadedwallpaper/.gitignore
@@ -1 +1,16 @@
+# --- canonical .gitignore (Steve standing rule, gitify sweep 2026-05-30) ---
+node_modules/
 .env
+.env*
+.env.local
+dist/
+build/
+.next/
+*.log
+.DS_Store
+tmp/
+*.bak
+*.bak-*
+.claude/
+.vercel
+.vercel/
diff --git a/US-001-IMPLEMENTATION.md.pre-scrub-2026-05-07.bak b/US-001-IMPLEMENTATION.md.pre-scrub-2026-05-07.bak
deleted file mode 100644
index 749b276f..00000000
--- a/US-001-IMPLEMENTATION.md.pre-scrub-2026-05-07.bak
+++ /dev/null
@@ -1,194 +0,0 @@
-# US-001: Shopify API Integration Service - Implementation Summary
-
-## Status: ✅ COMPLETED
-
-## Story Details
-**Title:** Create Shopify API Integration Service
-**Description:** As a system, I want a service to connect to Shopify Admin API so that I can retrieve and update product information programmatically.
-
-## Acceptance Criteria - All Met ✅
-- ✅ Service connects to Shopify Admin API with proper authentication
-- ✅ Can fetch product lists by vendor with pagination
-- ✅ Can update individual product fields (title, description, tags, type, category)
-- ✅ Handles API rate limiting with appropriate delays
-- ✅ Typecheck passes
-
-## Implementation Details
-
-### Files Modified
-1. **lib/shopify-client.ts** - Enhanced with new update methods
-   - Added `updateProductTitle(productId, title)` - Update product title
-   - Added `updateProductDescription(productId, description)` - Update product description (body_html)
-   - Added `updateProductType(productId, productType)` - Update product type
-   - Added `updateProduct(productId, updates)` - Batch update multiple fields
-   - Enhanced `ShopifyProduct` interface to include `body_html` field
-   - Existing methods: `getProducts()`, `getAllProducts()`, `updateProductTags()`
-
-2. **scripts/test-us-001-shopify-service.ts** - Comprehensive test suite
-   - Tests authentication and connection
-   - Tests vendor filtering with pagination
-   - Tests automatic pagination (getAllProducts)
-   - Verifies rate limiting (2 calls/second max)
-   - Documents all update methods
-
-### Key Features
-
-#### Authentication
-- Uses environment variables: `SHOPIFY_ADMIN_ACCESS_TOKEN`, `SHOPIFY_STORE_DOMAIN`, `SHOPIFY_ADMIN_API_VERSION`
-- Secure token-based authentication via X-Shopify-Access-Token header
-- Configuration in `.env` file
-
-#### Rate Limiting
-- Enforces 500ms minimum between requests (2 calls/second max)
-- Automatic delay insertion between consecutive API calls
-- Prevents API rate limit errors
-
-#### Pagination
-- Manual pagination via `getProducts({ vendor, limit, sinceId })`
-- Automatic pagination via `getAllProducts(vendor)` - retrieves all products
-- Uses `since_id` parameter for cursor-based pagination
-- Maximum 250 products per request (Shopify limit)
-
-#### Update Methods
-1. **Single Field Updates:**
-   ```typescript
-   await client.updateProductTitle(productId, 'New Title');
-   await client.updateProductDescription(productId, '<p>New description</p>');
-   await client.updateProductTags(productId, ['Tag1', 'Tag2']);
-   await client.updateProductType(productId, 'Wallcovering');
-   ```
-
-2. **Batch Updates:**
-   ```typescript
-   await client.updateProduct(productId, {
-     title: 'New Title',
-     description: '<p>New description</p>',
-     tags: ['Tag1', 'Tag2'],
-     product_type: 'Wallcovering'
-   });
-   ```
-
-#### Error Handling
-- Typed error responses via `ShopifyApiError` interface
-- Network error detection and reporting
-- HTTP status code checking
-- Detailed error messages
-
-## Test Results
-
-### Test Run Output (Jan 22, 2026)
-```
-=== US-001: Shopify API Integration Service Test ===
-
-Test 1: Connection and authentication
-✅ Successfully connected to Shopify API
-   Retrieved 1 product(s)
-
-Test 2: Fetch products by vendor with pagination
-✅ Successfully fetched 10 products for vendor "Koroseal"
-   First product: "() | (DWK-31482-Sample) | Architectural Wallcoverings" (ID: 7584976699443)
-
-Test 3: Automatic pagination (getAllProducts)
-✅ Successfully fetched all products with pagination
-   Total products for "Phillipe Romano": 1527
-
-Test 4: Rate limiting verification
-   Making 5 consecutive requests to verify rate limiting...
-✅ Rate limiting working correctly (2170ms elapsed, expected >=2000ms)
-
-Test 5: Update methods verification
-   Available update methods:
-   ✅ updateProductTitle(productId, title)
-   ✅ updateProductDescription(productId, description)
-   ✅ updateProductTags(productId, tags[])
-   ✅ updateProductType(productId, productType)
-   ✅ updateProduct(productId, { title, description, tags, product_type })
-```
-
-### TypeScript Compilation
-```bash
-$ npm run build
-> designer-wallcoverings@1.0.0 build
-> tsc --noEmit
-
-# No errors - typecheck passes ✅
-```
-
-## Usage Examples
-
-### Fetch Products by Vendor
-```typescript
-import { ShopifyClient } from './lib/shopify-client';
-
-const client = new ShopifyClient();
-
-// Get first 10 products from a vendor
-const products = await client.getProducts({
-  vendor: 'Koroseal',
-  limit: 10
-});
-
-// Get all products from a vendor (with automatic pagination)
-const allProducts = await client.getAllProducts('Phillipe Romano');
-console.log(`Total products: ${allProducts.length}`);
-```
-
-### Update Product Fields
-```typescript
-import { ShopifyClient } from './lib/shopify-client';
-
-const client = new ShopifyClient();
-const productId = 7584976699443;
-
-// Update single field
-await client.updateProductTitle(productId, 'New Product Title');
-
-// Update multiple fields at once
-await client.updateProduct(productId, {
-  title: 'Updated Title',
-  description: '<p>Updated description with HTML</p>',
-  tags: ['Azure', 'Modern', 'Pattern'],
-  product_type: 'Wallcovering'
-});
-```
-
-## API Configuration
-
-### Environment Variables (.env)
-```bash
-SHOPIFY_STORE_DOMAIN=designer-laboratory-sandbox.myshopify.com
-SHOPIFY_ADMIN_API_VERSION=2024-01
-SHOPIFY_ADMIN_ACCESS_TOKEN=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
-```
-
-### Shopify API Endpoints Used
-- `GET /admin/api/2024-01/products.json` - List products
-- `GET /admin/api/2024-01/products/{id}.json` - Get single product
-- `PUT /admin/api/2024-01/products/{id}.json` - Update product
-
-## Git Commit
-```
-commit 0c06e65fe7611bd09009af9ea14043b7c7d79a27
-Author: Ralph <ralph@designer-wallcoverings.com>
-Date:   Thu Jan 22 15:27:05 2026 +0000
-
-    feat(US-001): Create Shopify API Integration Service
-
-    - Enhanced ShopifyClient with comprehensive update methods
-    - Added updateProductTitle() for title updates
-    - Added updateProductDescription() for description updates
-    - Added updateProductType() for product type updates
-    - Added updateProduct() for batch field updates
-    - Existing functionality: vendor filtering, pagination, rate limiting
-    - All acceptance criteria met and verified
-    - Typecheck passes
-
-    Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
-
- lib/shopify-client.ts                  | 103 +++++++++++++++++++++++++++
- scripts/test-us-001-shopify-service.ts |  99 ++++++++++++++++++++++++++
- 2 files changed, 202 insertions(+)
-```
-
-## Next Steps
-US-001 is complete and ready for US-002.
diff --git a/ai-enrich-arte-fix.js.pre-scrub-2026-05-07.bak b/ai-enrich-arte-fix.js.pre-scrub-2026-05-07.bak
deleted file mode 100644
index c8b391e9..00000000
--- a/ai-enrich-arte-fix.js.pre-scrub-2026-05-07.bak
+++ /dev/null
@@ -1,116 +0,0 @@
-const https = require('https');
-const { Client } = require('pg');
-
-const GEMINI_KEY = 'REDACTED_GEMINI_KEY';
-const GEMINI_URL = `https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key=${GEMINI_KEY}`;
-
-const PRODUCTS = [
-  {id:123,sku:'19433',name:'Horus Desert Sun',img:'https://edge.arte-international.com/media/4960d9f1-bc0a-4bcb-9a82-c09d9e5e7d98/conversions/EssentialsLuxor_Horus_19436_Roomshot_Web_LR-medium-two-thirds.jpg'},
-  {id:16,sku:'23401',name:'Amarna Bister',img:'https://edge.arte-international.com/media/cc56d30c-5e1c-4b22-878e-b437681e6f1b/conversions/Memphis_Amarna_23400_Roomshot_Web_LR-medium-two-thirds.jpg'},
-  {id:15,sku:'23412',name:'Sakkara Antique Gold',img:'https://edge.arte-international.com/media/a580214f-25ff-472e-b88c-6a2bd8dfab64/conversions/Memphis_Sakkara_23410_Roomshot_Web_LR-medium-two-thirds.jpg'},
-  {id:172,sku:'23413',name:'Sakkara Off-white',img:'https://edge.arte-international.com/media/a580214f-25ff-472e-b88c-6a2bd8dfab64/conversions/Memphis_Sakkara_23410_Roomshot_Web_LR-medium-two-thirds.jpg'},
-  {id:72,sku:'23421',name:'Ibis Biscuit',img:'https://edge.arte-international.com/media/2183e2bc-0620-43c4-81ae-6364abed3937/conversions/Memphis_Ibis_23420_Roomshot_Web_LR-medium-two-thirds.jpg'},
-  {id:75,sku:'23422',name:'Ibis Chestnut',img:'https://edge.arte-international.com/media/2183e2bc-0620-43c4-81ae-6364abed3937/conversions/Memphis_Ibis_23420_Roomshot_Web_LR-medium-two-thirds.jpg'},
-  {id:78,sku:'23431',name:'Siwa Bone',img:'https://edge.arte-international.com/media/428898de-d288-40f3-b463-1714c59de939/conversions/Memphis_Siwa_23432_Roomshot_Web_LR-medium-two-thirds.jpg'},
-  {id:83,sku:'23433',name:'Siwa Biscuit',img:'https://edge.arte-international.com/media/428898de-d288-40f3-b463-1714c59de939/conversions/Memphis_Siwa_23432_Roomshot_Web_LR-medium-two-thirds.jpg'},
-  {id:183,sku:'29221',name:'Galon Grey Linen',img:'https://edge.arte-international.com/media/8571da7a-7bf9-4abc-adf0-76863cee5413/conversions/Allures_Galon9224_1_Roomshot_Web_LR-medium-two-thirds.jpg'},
-];
-
-const PROMPT = `You are an interior design wallcovering product analyst. Analyze this wallcovering image and return ONLY valid JSON (no markdown, no backticks):
-{
-  "colors": ["<list 3-6 actual colors you see, e.g. Warm Beige, Soft Gold>"],
-  "backgroundColor": "<the single dominant background color>",
-  "styles": ["<1-3 design styles, e.g. Contemporary, Art Deco, Minimalist>"],
-  "patterns": ["<1-2 pattern types, e.g. Geometric, Textured, Striped>"],
-  "tags": ["<5-8 interior design tags, e.g. luxury, neutral, organic>"],
-  "description": "<2 sentences describing this wallcovering for a commercial interior designer>"
-}`;
-
-function fetchImage(url) {
-  return new Promise((resolve, reject) => {
-    https.get(url, { headers: { 'User-Agent': 'Mozilla/5.0' } }, (res) => {
-      if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
-        return fetchImage(res.headers.location).then(resolve).catch(reject);
-      }
-      if (res.statusCode !== 200) return reject(new Error(`HTTP ${res.statusCode}`));
-      const chunks = [];
-      res.on('data', c => chunks.push(c));
-      res.on('end', () => resolve(Buffer.concat(chunks)));
-      res.on('error', reject);
-    }).on('error', reject);
-  });
-}
-
-function callGemini(b64, name) {
-  const body = JSON.stringify({
-    contents: [{ parts: [
-      { text: `Product: ${name}\n\n${PROMPT}` },
-      { inlineData: { mimeType: 'image/jpeg', data: b64 } }
-    ]}],
-    generationConfig: { temperature: 0.2, maxOutputTokens: 1024 }
-  });
-  return new Promise((resolve, reject) => {
-    const url = new URL(GEMINI_URL);
-    const req = https.request({ hostname: url.hostname, path: url.pathname + url.search, method: 'POST', headers: { 'Content-Type': 'application/json' } }, (res) => {
-      let data = '';
-      res.on('data', c => data += c);
-      res.on('end', () => {
-        try {
-          const json = JSON.parse(data);
-          if (json.error) return reject(new Error(json.error.message));
-          const text = json.candidates?.[0]?.content?.parts?.[0]?.text || '';
-          resolve(JSON.parse(text.replace(/```json\n?/g, '').replace(/```\n?/g, '').trim()));
-        } catch (e) { reject(new Error(`Parse: ${e.message}`)); }
-      });
-    });
-    req.on('error', reject);
-    req.write(body);
-    req.end();
-  });
-}
-
-async function main() {
-  const db = new Client({ connectionString: 'postgresql://dw_admin:DW2024SecurePass@127.0.0.1:5432/dw_unified' });
-  await db.connect();
-  let ok = 0, fail = 0;
-
-  for (let i = 0; i < PRODUCTS.length; i++) {
-    const p = PRODUCTS[i];
-    console.log(`\n[${i+1}/9] ${p.sku} - ${p.name}`);
-    try {
-      const imgBuf = await fetchImage(p.img);
-      console.log(`  Image: ${(imgBuf.length/1024).toFixed(0)}KB`);
-      const ai = await callGemini(imgBuf.toString('base64'), p.name);
-      console.log(`  Colors: ${ai.colors.join(', ')} | BG: ${ai.backgroundColor}`);
-
-      // vendor_catalog: text columns - stringify arrays as JSON strings
-      await db.query(`
-        UPDATE vendor_catalog SET
-          ai_colors = $1, ai_background_color = $2, ai_styles = $3,
-          ai_patterns = $4, ai_tags = $5, ai_description = $6
-        WHERE id = $7
-      `, [JSON.stringify(ai.colors), ai.backgroundColor, JSON.stringify(ai.styles),
-          JSON.stringify(ai.patterns), JSON.stringify(ai.tags), ai.description, p.id]);
-
-      // arte_catalog: jsonb columns - also stringify for jsonb
-      const arteRes = await db.query(`
-        UPDATE arte_catalog SET
-          ai_colors = $1::jsonb, ai_background_color = $2, ai_styles = $3::jsonb,
-          ai_patterns = $4::jsonb, ai_tags = $5::jsonb, ai_description = $6
-        WHERE arte_sku = $7
-      `, [JSON.stringify(ai.colors), ai.backgroundColor, JSON.stringify(ai.styles),
-          JSON.stringify(ai.patterns), JSON.stringify(ai.tags), ai.description, p.sku]);
-
-      console.log(`  Saved vendor_catalog + arte_catalog (${arteRes.rowCount} arte row)`);
-      ok++;
-    } catch (err) {
-      console.log(`  FAIL: ${err.message}`);
-      fail++;
-    }
-    if (i < PRODUCTS.length - 1) await new Promise(r => setTimeout(r, 1200));
-  }
-
-  await db.end();
-  console.log(`\nDONE: ${ok}/9 success, ${fail} failed`);
-}
-main().catch(console.error);
diff --git a/ai-enrich-gaps2.js.pre-scrub-2026-05-07.bak b/ai-enrich-gaps2.js.pre-scrub-2026-05-07.bak
deleted file mode 100644
index 1302da2f..00000000
--- a/ai-enrich-gaps2.js.pre-scrub-2026-05-07.bak
+++ /dev/null
@@ -1,133 +0,0 @@
-const https = require('https');
-const http = require('http');
-const { Client } = require('pg');
-
-const GEMINI_KEY = 'REDACTED_GEMINI_KEY';
-const GEMINI_URL = `https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key=${GEMINI_KEY}`;
-
-const PRODUCTS = [
-  {id:123,vendor:'arte',sku:'19433',name:'Horus Desert Sun',img:'https://edge.arte-international.com/media/4960d9f1-bc0a-4bcb-9a82-c09d9e5e7d98/conversions/EssentialsLuxor_Horus_19436_Roomshot_Web_LR-medium-two-thirds.jpg'},
-  {id:16,vendor:'arte',sku:'23401',name:'Amarna Bister',img:'https://edge.arte-international.com/media/cc56d30c-5e1c-4b22-878e-b437681e6f1b/conversions/Memphis_Amarna_23400_Roomshot_Web_LR-medium-two-thirds.jpg'},
-  {id:15,vendor:'arte',sku:'23412',name:'Sakkara Antique Gold',img:'https://edge.arte-international.com/media/a580214f-25ff-472e-b88c-6a2bd8dfab64/conversions/Memphis_Sakkara_23410_Roomshot_Web_LR-medium-two-thirds.jpg'},
-  {id:172,vendor:'arte',sku:'23413',name:'Sakkara Off-white',img:'https://edge.arte-international.com/media/a580214f-25ff-472e-b88c-6a2bd8dfab64/conversions/Memphis_Sakkara_23410_Roomshot_Web_LR-medium-two-thirds.jpg'},
-  {id:72,vendor:'arte',sku:'23421',name:'Ibis Biscuit',img:'https://edge.arte-international.com/media/2183e2bc-0620-43c4-81ae-6364abed3937/conversions/Memphis_Ibis_23420_Roomshot_Web_LR-medium-two-thirds.jpg'},
-  {id:75,vendor:'arte',sku:'23422',name:'Ibis Chestnut',img:'https://edge.arte-international.com/media/2183e2bc-0620-43c4-81ae-6364abed3937/conversions/Memphis_Ibis_23420_Roomshot_Web_LR-medium-two-thirds.jpg'},
-  {id:78,vendor:'arte',sku:'23431',name:'Siwa Bone',img:'https://edge.arte-international.com/media/428898de-d288-40f3-b463-1714c59de939/conversions/Memphis_Siwa_23432_Roomshot_Web_LR-medium-two-thirds.jpg'},
-  {id:83,vendor:'arte',sku:'23433',name:'Siwa Biscuit',img:'https://edge.arte-international.com/media/428898de-d288-40f3-b463-1714c59de939/conversions/Memphis_Siwa_23432_Roomshot_Web_LR-medium-two-thirds.jpg'},
-  {id:183,vendor:'arte',sku:'29221',name:'Galon Grey Linen',img:'https://edge.arte-international.com/media/8571da7a-7bf9-4abc-adf0-76863cee5413/conversions/Allures_Galon9224_1_Roomshot_Web_LR-medium-two-thirds.jpg'},
-  {id:1751,vendor:'black_edition',sku:'W924/01',name:'Mizumi Panel Carbon',img:'https://static.theromogroup.com/rb/cache/image/720x720/catalog/product/W/9/W924-01FP-mizumi-panel-charcoal_00.jpg'},
-  {id:1752,vendor:'black_edition',sku:'W924/02',name:'Mizumi Panel Midnight',img:'https://static.theromogroup.com/rb/cache/image/720x720/catalog/product/W/9/W924-02FP-mizumi-panel-midnight_00.jpg'},
-  {id:1753,vendor:'black_edition',sku:'W924/03',name:'Mizumi Panel Basalt',img:'https://static.theromogroup.com/rb/cache/image/720x720/catalog/product/W/9/W924-03FP-mizumi-panel-stone_00.jpg'},
-  {id:1754,vendor:'black_edition',sku:'W924/04',name:'Mizumi Panel Viridian',img:'https://static.theromogroup.com/rb/cache/image/720x720/catalog/product/W/9/W924-04FP-mizumi-panel-teal_02.jpg'},
-];
-
-const PROMPT = `You are an interior design wallcovering product analyst. Analyze this wallcovering image and return ONLY valid JSON (no markdown, no backticks):
-{
-  "colors": ["<list 3-6 actual colors you see, e.g. Warm Beige, Soft Gold>"],
-  "backgroundColor": "<the single dominant background color>",
-  "styles": ["<1-3 design styles, e.g. Contemporary, Art Deco, Minimalist>"],
-  "patterns": ["<1-2 pattern types, e.g. Geometric, Textured, Striped>"],
-  "tags": ["<5-8 interior design tags, e.g. luxury, neutral, organic>"],
-  "description": "<2 sentences describing this wallcovering for a commercial interior designer>"
-}`;
-
-function fetchImage(url) {
-  const mod = url.startsWith('https') ? https : http;
-  return new Promise((resolve, reject) => {
-    mod.get(url, { headers: { 'User-Agent': 'Mozilla/5.0' } }, (res) => {
-      if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
-        return fetchImage(res.headers.location).then(resolve).catch(reject);
-      }
-      if (res.statusCode !== 200) return reject(new Error(`HTTP ${res.statusCode}`));
-      const chunks = [];
-      res.on('data', c => chunks.push(c));
-      res.on('end', () => resolve(Buffer.concat(chunks)));
-      res.on('error', reject);
-    }).on('error', reject);
-  });
-}
-
-function callGemini(b64, productName) {
-  const body = JSON.stringify({
-    contents: [{ parts: [
-      { text: `Product: ${productName}\n\n${PROMPT}` },
-      { inlineData: { mimeType: 'image/jpeg', data: b64 } }
-    ]}],
-    generationConfig: { temperature: 0.2, maxOutputTokens: 1024 }
-  });
-
-  return new Promise((resolve, reject) => {
-    const url = new URL(GEMINI_URL);
-    const req = https.request({
-      hostname: url.hostname, path: url.pathname + url.search,
-      method: 'POST',
-      headers: { 'Content-Type': 'application/json' }
-    }, (res) => {
-      let data = '';
-      res.on('data', c => data += c);
-      res.on('end', () => {
-        try {
-          const json = JSON.parse(data);
-          if (json.error) return reject(new Error(json.error.message));
-          const text = json.candidates?.[0]?.content?.parts?.[0]?.text || '';
-          const clean = text.replace(/```json\n?/g, '').replace(/```\n?/g, '').trim();
-          resolve(JSON.parse(clean));
-        } catch (e) { reject(new Error(`Parse: ${e.message}`)); }
-      });
-    });
-    req.on('error', reject);
-    req.write(body);
-    req.end();
-  });
-}
-
-async function main() {
-  const db = new Client({ connectionString: 'postgresql://dw_admin:DW2024SecurePass@127.0.0.1:5432/dw_unified' });
-  await db.connect();
-
-  let success = 0, fail = 0;
-
-  for (let i = 0; i < PRODUCTS.length; i++) {
-    const p = PRODUCTS[i];
-    console.log(`\n[${i+1}/13] ${p.vendor} ${p.sku} - ${p.name}`);
-
-    try {
-      const imgBuf = await fetchImage(p.img);
-      console.log(`  Image: ${(imgBuf.length/1024).toFixed(0)}KB`);
-      const b64 = imgBuf.toString('base64');
-
-      const ai = await callGemini(b64, p.name);
-      console.log(`  Colors: ${(ai.colors||[]).join(', ')}`);
-      console.log(`  BG: ${ai.backgroundColor} | Styles: ${(ai.styles||[]).join(', ')}`);
-
-      const res = await db.query(`
-        UPDATE vendor_catalog SET
-          ai_colors = $1, ai_background_color = $2, ai_styles = $3,
-          ai_patterns = $4, ai_tags = $5, ai_description = $6
-        WHERE id = $7
-      `, [ai.colors, ai.backgroundColor, ai.styles, ai.patterns, ai.tags, ai.description, p.id]);
-
-      if (p.vendor === 'arte') {
-        await db.query(`
-          UPDATE arte_catalog SET
-            ai_colors = $1, ai_background_color = $2, ai_styles = $3,
-            ai_patterns = $4, ai_tags = $5, ai_description = $6
-          WHERE arte_sku = $7
-        `, [ai.colors, ai.backgroundColor, ai.styles, ai.patterns, ai.tags, ai.description, p.sku]);
-      }
-
-      console.log(`  Saved (${res.rowCount} row)`);
-      success++;
-    } catch (err) {
-      console.log(`  FAIL: ${err.message}`);
-      fail++;
-    }
-
-    if (i < PRODUCTS.length - 1) await new Promise(r => setTimeout(r, 1200));
-  }
-
-  await db.end();
-  console.log(`\nDONE: ${success} success, ${fail} failed out of 13`);
-}
-
-main().catch(console.error);
diff --git a/arte-fill-tags.js.pre-scrub-2026-05-07.bak b/arte-fill-tags.js.pre-scrub-2026-05-07.bak
deleted file mode 100644
index 978ced98..00000000
--- a/arte-fill-tags.js.pre-scrub-2026-05-07.bak
+++ /dev/null
@@ -1,123 +0,0 @@
-const https = require('https');
-const http = require('http');
-const { Client } = require('pg');
-
-const GEMINI_KEY = 'REDACTED_GEMINI_KEY';
-const GEMINI_MODEL = 'gemini-2.0-flash';
-const DB = 'postgresql://dw_admin:DW2024SecurePass@127.0.0.1:5432/dw_unified';
-
-async function geminiAnalyze(imageUrl, patternName) {
-  const prompt = `You are an interior design wallcovering tagger. Analyze this wallcovering image.
-
-Product: ${patternName}
-
-Return ONLY a JSON object with:
-{
-  "tags": ["tag1", "tag2", ...] // 5-10 interior design tags (e.g., "Botanical", "Luxury", "Traditional", "Textured", "Neutral Palette", "Organic", "Nature-Inspired")
-}
-
-Tags should describe: style, mood, pattern type, texture, suitable rooms, design era. Be specific to wallcoverings/interior design.`;
-
-  const body = JSON.stringify({
-    contents: [{
-      parts: [
-        { text: prompt },
-        { inline_data: { mime_type: 'image/jpeg', data: await fetchImageBase64(imageUrl) } }
-      ]
-    }],
-    generationConfig: { temperature: 0.2, maxOutputTokens: 300 }
-  });
-
-  return new Promise((resolve, reject) => {
-    const url = `https://generativelanguage.googleapis.com/v1beta/models/${GEMINI_MODEL}:generateContent?key=${GEMINI_KEY}`;
-    const parsed = new URL(url);
-    const req = https.request({
-      hostname: parsed.hostname,
-      path: parsed.pathname + parsed.search,
-      method: 'POST',
-      headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(body) }
-    }, (res) => {
-      let data = '';
-      res.on('data', c => data += c);
-      res.on('end', () => {
-        try {
-          const json = JSON.parse(data);
-          const text = json.candidates?.[0]?.content?.parts?.[0]?.text || '';
-          const cleaned = text.replace(/```json\n?/g, '').replace(/```\n?/g, '').trim();
-          resolve(JSON.parse(cleaned));
-        } catch(e) { reject(new Error(`Parse error: ${e.message} raw: ${data.substring(0,200)}`)); }
-      });
-    });
-    req.on('error', reject);
-    req.write(body);
-    req.end();
-  });
-}
-
-function fetchImageBase64(imageUrl) {
-  return new Promise((resolve, reject) => {
-    const mod = imageUrl.startsWith('https') ? https : http;
-    mod.get(imageUrl, { headers: { 'User-Agent': 'Mozilla/5.0' } }, (res) => {
-      if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
-        return fetchImageBase64(res.headers.location).then(resolve).catch(reject);
-      }
-      const chunks = [];
-      res.on('data', c => chunks.push(c));
-      res.on('end', () => resolve(Buffer.concat(chunks).toString('base64')));
-    }).on('error', reject);
-  });
-}
-
-function sleep(ms) { return new Promise(r => setTimeout(r, ms)); }
-
-async function main() {
-  const db = new Client(DB);
-  await db.connect();
-
-  const { rows } = await db.query(`
-    SELECT id, mfr_sku, pattern_name, image_url
-    FROM vendor_catalog 
-    WHERE vendor_code = 'arte' 
-      AND (ai_tags IS NULL OR ai_tags = '[]'::jsonb)
-      AND image_url IS NOT NULL AND image_url != ''
-    ORDER BY mfr_sku
-  `);
-
-  console.log(`Processing ${rows.length} Arte products for ai_tags...`);
-  let success = 0, fail = 0;
-
-  for (const row of rows) {
-    try {
-      console.log(`  [${success+fail+1}/${rows.length}] ${row.mfr_sku} ${row.pattern_name}...`);
-      const result = await geminiAnalyze(row.image_url, row.pattern_name);
-      
-      if (result.tags && Array.isArray(result.tags) && result.tags.length > 0) {
-        await db.query(
-          `UPDATE vendor_catalog SET ai_tags = $1 WHERE id = $2`,
-          [JSON.stringify(result.tags), row.id]
-        );
-        console.log(`    ✓ ${result.tags.length} tags: ${result.tags.join(', ')}`);
-        success++;
-      } else {
-        console.log(`    ✗ No tags returned`);
-        fail++;
-      }
-      await sleep(1200); // rate limit
-    } catch(e) {
-      console.log(`    ✗ Error: ${e.message}`);
-      fail++;
-    }
-  }
-
-  // Handle 67403 (no image) - mark for review
-  await db.query(`
-    UPDATE vendor_catalog 
-    SET ai_tags = '["Needs-Image-Review"]'::jsonb
-    WHERE vendor_code = 'arte' AND mfr_sku = '67403' AND ai_colors IS NULL
-  `);
-
-  console.log(`\nDone! Success: ${success}, Failed: ${fail}, Skipped (no image): 1`);
-  await db.end();
-}
-
-main().catch(e => { console.error(e); process.exit(1); });
diff --git a/enrich-arte-fix.js.pre-scrub-2026-05-07.bak b/enrich-arte-fix.js.pre-scrub-2026-05-07.bak
deleted file mode 100644
index 5b3c484d..00000000
--- a/enrich-arte-fix.js.pre-scrub-2026-05-07.bak
+++ /dev/null
@@ -1,139 +0,0 @@
-const https = require('https');
-const { Client } = require('pg');
-
-const GEMINI_KEY = 'REDACTED_GEMINI_KEY';
-const GEMINI_URL = `https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key=${GEMINI_KEY}`;
-const DB_URL = 'postgresql://dw_admin:DW2024SecurePass@127.0.0.1:5432/dw_unified';
-
-const PRODUCTS = [
-  { id: 123, sku: '19433', pattern: 'Horus', color: 'Desert Sun', img: 'https://edge.arte-international.com/media/4960d9f1-bc0a-4bcb-9a82-c09d9e5e7d98/conversions/EssentialsLuxor_Horus_19436_Roomshot_Web_LR-medium-two-thirds.jpg' },
-  { id: 16, sku: '23401', pattern: 'Amarna', color: 'Bister', img: 'https://edge.arte-international.com/media/cc56d30c-5e1c-4b22-878e-b437681e6f1b/conversions/Memphis_Amarna_23400_Roomshot_Web_LR-medium-two-thirds.jpg' },
-  { id: 15, sku: '23412', pattern: 'Sakkara', color: 'Antique Gold', img: 'https://edge.arte-international.com/media/a580214f-25ff-472e-b88c-6a2bd8dfab64/conversions/Memphis_Sakkara_23410_Roomshot_Web_LR-medium-two-thirds.jpg' },
-  { id: 172, sku: '23413', pattern: 'Sakkara', color: 'Off-white', img: 'https://edge.arte-international.com/media/a580214f-25ff-472e-b88c-6a2bd8dfab64/conversions/Memphis_Sakkara_23410_Roomshot_Web_LR-medium-two-thirds.jpg' },
-  { id: 72, sku: '23421', pattern: 'Ibis', color: 'Biscuit', img: 'https://edge.arte-international.com/media/2183e2bc-0620-43c4-81ae-6364abed3937/conversions/Memphis_Ibis_23420_Roomshot_Web_LR-medium-two-thirds.jpg' },
-  { id: 75, sku: '23422', pattern: 'Ibis', color: 'Chestnut', img: 'https://edge.arte-international.com/media/2183e2bc-0620-43c4-81ae-6364abed3937/conversions/Memphis_Ibis_23420_Roomshot_Web_LR-medium-two-thirds.jpg' },
-  { id: 78, sku: '23431', pattern: 'Siwa', color: 'Bone', img: 'https://edge.arte-international.com/media/428898de-d288-40f3-b463-1714c59de939/conversions/Memphis_Siwa_23432_Roomshot_Web_LR-medium-two-thirds.jpg' },
-  { id: 83, sku: '23433', pattern: 'Siwa', color: 'Biscuit', img: 'https://edge.arte-international.com/media/428898de-d288-40f3-b463-1714c59de939/conversions/Memphis_Siwa_23432_Roomshot_Web_LR-medium-two-thirds.jpg' },
-  { id: 183, sku: '29221', pattern: 'Galon', color: 'Grey Linen', img: 'https://edge.arte-international.com/media/8571da7a-7bf9-4abc-adf0-76863cee5413/conversions/Allures_Galon9224_1_Roomshot_Web_LR-medium-two-thirds.jpg' },
-];
-
-function downloadImage(url) {
-  return new Promise((resolve, reject) => {
-    https.get(url, { headers: { 'User-Agent': 'Mozilla/5.0' } }, (res) => {
-      if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
-        return downloadImage(res.headers.location).then(resolve).catch(reject);
-      }
-      const chunks = [];
-      res.on('data', c => chunks.push(c));
-      res.on('end', () => resolve(Buffer.concat(chunks)));
-      res.on('error', reject);
-    }).on('error', reject);
-  });
-}
-
-function callGemini(b64, mime, prompt) {
-  return new Promise((resolve, reject) => {
-    const body = JSON.stringify({
-      contents: [{ parts: [
-        { text: prompt },
-        { inline_data: { mime_type: mime, data: b64 } }
-      ]}]
-    });
-    const url = new URL(GEMINI_URL);
-    const req = https.request({
-      hostname: url.hostname,
-      path: url.pathname + url.search,
-      method: 'POST',
-      headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(body) },
-      timeout: 30000
-    }, (res) => {
-      const chunks = [];
-      res.on('data', c => chunks.push(c));
-      res.on('end', () => {
-        try { resolve(JSON.parse(Buffer.concat(chunks).toString())); }
-        catch(e) { reject(e); }
-      });
-    });
-    req.on('error', reject);
-    req.on('timeout', () => { req.destroy(); reject(new Error('timeout')); });
-    req.write(body);
-    req.end();
-  });
-}
-
-function parseGeminiJson(text) {
-  let clean = text.replace(/^```(?:json)?\s*/m, '').replace(/\s*```\s*$/m, '').trim();
-  return JSON.parse(clean);
-}
-
-// For vendor_catalog (text columns): PostgreSQL array literal
-function toPgArray(arr) {
-  if (!arr || !arr.length) return '{}';
-  return '{' + arr.map(s => '"' + s.replace(/"/g, '\\"') + '"').join(',') + '}';
-}
-
-async function main() {
-  const db = new Client({ connectionString: DB_URL });
-  await db.connect();
-  let success = 0, fail = 0;
-
-  for (let i = 0; i < PRODUCTS.length; i++) {
-    const p = PRODUCTS[i];
-    console.log(`\n━━━ [${i+1}/9] arte ${p.sku} (${p.pattern} - ${p.color}) ━━━`);
-    try {
-      const imgBuf = await downloadImage(p.img);
-      if (!imgBuf.length) throw new Error('Empty image');
-      const b64 = imgBuf.toString('base64');
-      console.log(`  Downloaded: ${(imgBuf.length / 1024).toFixed(0)}KB`);
-
-      const prompt = `You are an interior design wallcovering expert. Analyze this wallcovering image. The vendor color name is "${p.color}" and the pattern is "${p.pattern}".
-
-Return ONLY valid JSON with no markdown formatting:
-{
-  "colors": ["list 3-6 actual colors visible in the wallcovering"],
-  "backgroundColor": "the dominant background color",
-  "tags": ["5-8 interior design tags like Luxury, Textured, Modern, Elegant"],
-  "styles": ["1-3 design period styles like Contemporary, Art Deco, Mid-Century"],
-  "patterns": ["1-3 pattern types like Geometric, Textured, Striped, Floral"],
-  "description": "Two-sentence commercial description for an architectural wallcovering retailer. Never use the word wallpaper."
-}`;
-
-      const resp = await callGemini(b64, 'image/jpeg', prompt);
-      const text = resp?.candidates?.[0]?.content?.parts?.[0]?.text;
-      if (!text) throw new Error('No Gemini text: ' + JSON.stringify(resp?.error || 'unknown').slice(0, 200));
-
-      const data = parseGeminiJson(text);
-      console.log(`  Colors: ${data.colors?.join(', ')}`);
-      console.log(`  BG: ${data.backgroundColor}`);
-      console.log(`  Tags: ${data.tags?.join(', ')}`);
-
-      // vendor_catalog uses TEXT columns (pg array format)
-      await db.query(`UPDATE vendor_catalog SET ai_colors=$1, ai_background_color=$2, ai_tags=$3, ai_styles=$4, ai_patterns=$5, ai_description=$6 WHERE id=$7`,
-        [toPgArray(data.colors), data.backgroundColor||'', toPgArray(data.tags), toPgArray(data.styles), toPgArray(data.patterns), data.description||'', p.id]);
-
-      // arte_catalog uses JSONB columns (JSON format)
-      await db.query(`UPDATE arte_catalog SET ai_colors=$1::jsonb, ai_background_color=$2, ai_tags=$3::jsonb, ai_styles=$4::jsonb, ai_patterns=$5::jsonb, ai_description=$6 WHERE arte_sku=$7`,
-        [JSON.stringify(data.colors), data.backgroundColor||'', JSON.stringify(data.tags), JSON.stringify(data.styles), JSON.stringify(data.patterns), data.description||'', p.sku]);
-
-      // Verify both
-      const v1 = await db.query('SELECT ai_colors FROM vendor_catalog WHERE id=$1', [p.id]);
-      const v2 = await db.query('SELECT ai_colors FROM arte_catalog WHERE arte_sku=$1', [p.sku]);
-      const ok1 = v1.rows[0]?.ai_colors && v1.rows[0].ai_colors !== '{}';
-      const ok2 = v2.rows[0]?.ai_colors && JSON.stringify(v2.rows[0].ai_colors) !== '[]';
-      if (ok1 && ok2) {
-        console.log(`  ✅ Saved (vendor_catalog + arte_catalog)`); success++;
-      } else {
-        console.log(`  ⚠️ Partial: vc=${ok1}, ac=${ok2}`); fail++;
-      }
-    } catch (err) {
-      console.log(`  ❌ Error: ${err.message}`); fail++;
-    }
-    await new Promise(r => setTimeout(r, 1200));
-  }
-
-  await db.end();
-  console.log(`\n━━━━━━━━━━━━━━━━━━━━━━`);
-  console.log(`RESULTS: ✅ ${success}  ❌ ${fail}  (of 9)`);
-}
-
-main().catch(e => { console.error(e); process.exit(1); });
diff --git a/enrich-arte-gaps.js.pre-scrub-2026-05-07.bak b/enrich-arte-gaps.js.pre-scrub-2026-05-07.bak
deleted file mode 100644
index 6f275bff..00000000
--- a/enrich-arte-gaps.js.pre-scrub-2026-05-07.bak
+++ /dev/null
@@ -1,124 +0,0 @@
-const https = require('https');
-const { Client } = require('pg');
-
-const GEMINI_KEY = 'REDACTED_GEMINI_KEY';
-const GEMINI_URL = `https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key=${GEMINI_KEY}`;
-const pgClient = new Client('postgresql://dw_admin:DW2024SecurePass@127.0.0.1:5432/dw_unified');
-
-const PROMPT = `You are an interior design wallcovering analyst. Analyze this wallcovering product image.
-
-Return ONLY valid JSON with these fields:
-{
-  "colors": ["<list 3-6 colors visible, e.g. Gold, Cream, Charcoal>"],
-  "backgroundColor": "<the single dominant background color>",
-  "tags": ["<5-8 interior design tags, e.g. Luxury, Textured, Hospitality, Modern>"],
-  "styles": ["<1-3 design styles, e.g. Contemporary, Art Deco, Minimalist>"],
-  "patterns": ["<1-3 pattern types, e.g. Geometric, Solid, Striped, Textured>"],
-  "description": "<2 sentence commercial description for architects and interior designers>"
-}
-
-Do NOT include markdown formatting. Return raw JSON only.`;
-
-const ARTE = [
-  { sku:'19433', url:'https://edge.arte-international.com/media/a0686a22-83f6-46a5-86fe-b17566b88245/conversions/EssentialsLuxor_Horus_19433_Packshot_Web_LR-medium-two-thirds.jpg' },
-  { sku:'23401', url:'https://edge.arte-international.com/media/d4610242-66e0-4b12-8723-df90ddb40e04/conversions/Memphis_Amarna_23401_Packshot_Web_HR-medium-two-thirds.jpg' },
-  { sku:'23412', url:'https://edge.arte-international.com/media/6fe8a7ec-ecdd-49c4-b398-223293c48eed/conversions/Memphis_Sakkara_23412_Packshot_Web_HR-medium-two-thirds.jpg' },
-  { sku:'23413', url:'https://edge.arte-international.com/media/a45badcf-d133-44b1-bcd6-1d74b4f13141/conversions/Memphis_Sakkara_23413_Packshot_Web_HR-medium-two-thirds.jpg' },
-  { sku:'23421', url:'https://edge.arte-international.com/media/fb1738af-4e34-4d36-9a3e-7392f590381a/conversions/Memphis_Ibis_23421_Packshot_Web_LR-medium-two-thirds.jpg' },
-  { sku:'23422', url:'https://edge.arte-international.com/media/2899330e-abba-481e-9098-ffd76f1731cf/conversions/Memphis_Ibis_23422_Packshot_Web_LR-medium-two-thirds.jpg' },
-  { sku:'23431', url:'https://edge.arte-international.com/media/2ef17968-181d-4da5-8c8a-59f719a3c0fd/conversions/Memphis_Siwa_23431_Packshot_Web_LR-medium-two-thirds.jpg' },
-  { sku:'23433', url:'https://edge.arte-international.com/media/fac5d958-0b0e-437f-abbe-a07a2c147676/conversions/Memphis_Siwa_23433_Packshot_Web_LR-medium-two-thirds.jpg' },
-  { sku:'29221', url:'https://edge.arte-international.com/media/f92ad97a-a59b-431c-84ad-0a7520995f6a/conversions/Allures_Galon_29221_Flatshot_Web_LR-medium-two-thirds.jpg' },
-];
-
-function fetchB64(url) {
-  return new Promise((resolve, reject) => {
-    const opts = new URL(url);
-    const req = https.get({
-      hostname: opts.hostname, path: opts.pathname + opts.search,
-      timeout: 15000,
-      headers: {
-        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
-        'Accept': 'image/webp,image/apng,image/*,*/*;q=0.8',
-        'Referer': 'https://www.arte-international.com/',
-      }
-    }, (res) => {
-      if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location)
-        return fetchB64(res.headers.location).then(resolve).catch(reject);
-      if (res.statusCode !== 200) return reject(new Error(`HTTP ${res.statusCode}`));
-      const c = []; res.on('data', d => c.push(d));
-      res.on('end', () => resolve(Buffer.concat(c).toString('base64')));
-      res.on('error', reject);
-    });
-    req.on('error', reject);
-  });
-}
-
-function callGemini(b64) {
-  const body = JSON.stringify({
-    contents: [{ parts: [
-      { text: PROMPT },
-      { inline_data: { mime_type: 'image/jpeg', data: b64 } }
-    ]}],
-    generationConfig: { temperature: 0.2, maxOutputTokens: 1024 }
-  });
-  return new Promise((resolve, reject) => {
-    const u = new URL(GEMINI_URL);
-    const req = https.request({
-      hostname: u.hostname, path: u.pathname + u.search,
-      method: 'POST', headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(body) },
-      timeout: 30000,
-    }, (res) => {
-      const c = []; res.on('data', d => c.push(d));
-      res.on('end', () => {
-        try {
-          const data = JSON.parse(Buffer.concat(c).toString());
-          if (data.error) return reject(new Error(data.error.message));
-          const text = data.candidates?.[0]?.content?.parts?.[0]?.text || '';
-          const clean = text.replace(/```json\n?/g,'').replace(/```\n?/g,'').trim();
-          resolve(JSON.parse(clean));
-        } catch(e) { reject(e); }
-      });
-    });
-    req.on('error', reject); req.write(body); req.end();
-  });
-}
-
-const sleep = ms => new Promise(r => setTimeout(r, ms));
-
-async function main() {
-  await pgClient.connect();
-  let ok = 0, fail = 0;
-
-  for (const p of ARTE) {
-    try {
-      process.stdout.write(`Arte ${p.sku}... `);
-      const b64 = await fetchB64(p.url);
-      const ai = await callGemini(b64);
-      
-      // Update vendor_catalog
-      await pgClient.query(`UPDATE vendor_catalog SET
-        ai_colors=$1, ai_background_color=$2, ai_styles=$3,
-        ai_patterns=$4, ai_tags=$5, ai_description=$6
-        WHERE vendor_code='arte' AND mfr_sku=$7`,
-        [JSON.stringify(ai.colors), ai.backgroundColor, JSON.stringify(ai.styles),
-         JSON.stringify(ai.patterns), JSON.stringify(ai.tags), ai.description, p.sku]);
-      
-      // Update arte_catalog
-      await pgClient.query(`UPDATE arte_catalog SET
-        ai_colors=$1::jsonb, ai_background_color=$2, ai_styles=$3::jsonb,
-        ai_patterns=$4::jsonb, ai_tags=$5::jsonb, ai_description=$6, ai_accepted_at=NOW()
-        WHERE arte_sku=$7`,
-        [JSON.stringify(ai.colors), ai.backgroundColor, JSON.stringify(ai.styles),
-         JSON.stringify(ai.patterns), JSON.stringify(ai.tags), ai.description, p.sku]);
-      
-      console.log(`OK  colors=[${ai.colors.join(', ')}] bg=${ai.backgroundColor}`);
-      ok++; await sleep(1200);
-    } catch(e) { console.log(`FAIL ${e.message}`); fail++; }
-  }
-
-  console.log(`\nDONE: ${ok}/9 Arte enriched, ${fail} failed`);
-  await pgClient.end();
-}
-
-main().catch(e => { console.error(e); process.exit(1); });
diff --git a/enrich-arte-puppeteer.js.pre-scrub-2026-05-07.bak b/enrich-arte-puppeteer.js.pre-scrub-2026-05-07.bak
deleted file mode 100644
index c4c59c52..00000000
--- a/enrich-arte-puppeteer.js.pre-scrub-2026-05-07.bak
+++ /dev/null
@@ -1,164 +0,0 @@
-const puppeteer = require('puppeteer');
-const https = require('https');
-const { Client } = require('pg');
-
-const GEMINI_KEY = 'REDACTED_GEMINI_KEY';
-const GEMINI_MODEL = 'gemini-2.0-flash';
-const PG_URL = 'postgresql://dw_admin:DW2024SecurePass@127.0.0.1:5432/dw_unified';
-
-const ARTE_PRODUCTS = [
-  { vc_id: 123, sku: '19433', name: 'Horus Desert Sun', url: 'https://www.arte-international.com/en/collections/essentials-luxor/horus/19433' },
-  { vc_id: 16,  sku: '23401', name: 'Amarna Bister', url: 'https://www.arte-international.com/en/collections/memphis/amarna/23401' },
-  { vc_id: 15,  sku: '23412', name: 'Sakkara Antique Gold', url: 'https://www.arte-international.com/en/collections/memphis/sakkara/23412' },
-  { vc_id: 172, sku: '23413', name: 'Sakkara Off-white', url: 'https://www.arte-international.com/en/collections/memphis/sakkara/23413' },
-  { vc_id: 72,  sku: '23421', name: 'Ibis Biscuit', url: 'https://www.arte-international.com/en/collections/memphis/ibis/23421' },
-  { vc_id: 75,  sku: '23422', name: 'Ibis Chestnut', url: 'https://www.arte-international.com/en/collections/memphis/ibis/23422' },
-  { vc_id: 78,  sku: '23431', name: 'Siwa Bone', url: 'https://www.arte-international.com/en/collections/memphis/siwa/23431' },
-  { vc_id: 83,  sku: '23433', name: 'Siwa Biscuit', url: 'https://www.arte-international.com/en/collections/memphis/siwa/23433' },
-  { vc_id: 183, sku: '29221', name: 'Galon Grey Linen', url: 'https://www.arte-international.com/en/collections/allures/galon/29221' },
-];
-
-const PROMPT = `You are an interior design color analyst for luxury wallcoverings.
-
-Analyze this wallcovering image and return a JSON object with these fields:
-- "colors": array of 3-6 prominent colors visible (e.g. ["Gold", "Ivory", "Charcoal"])
-- "backgroundColor": the single dominant background color (e.g. "Ivory")
-- "styles": array of 1-3 design styles (e.g. ["Contemporary", "Art Deco"])
-- "patterns": array of 1-2 pattern types (e.g. ["Geometric", "Textured"])
-- "tags": array of 4-8 interior design tags (e.g. ["luxury", "metallic", "hospitality", "accent wall"])
-- "description": a 2-sentence commercial description for an interior designer audience. NEVER use the word "wallpaper" — always say "wallcovering".
-
-Return ONLY the JSON object, no markdown fences, no explanation.`;
-
-function callGemini(imageBase64, mimeType) {
-  return new Promise((resolve, reject) => {
-    const body = JSON.stringify({
-      contents: [{ parts: [
-        { text: PROMPT },
-        { inline_data: { mime_type: mimeType || 'image/jpeg', data: imageBase64 } }
-      ]}],
-      generationConfig: { temperature: 0.3, maxOutputTokens: 1024 }
-    });
-    const url = new URL(`https://generativelanguage.googleapis.com/v1beta/models/${GEMINI_MODEL}:generateContent?key=${GEMINI_KEY}`);
-    const opts = { method: 'POST', hostname: url.hostname, path: url.pathname + url.search, headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(body) } };
-    const req = https.request(opts, (res) => {
-      const chunks = [];
-      res.on('data', c => chunks.push(c));
-      res.on('end', () => {
-        try {
-          const data = JSON.parse(Buffer.concat(chunks).toString());
-          if (data.error) return reject(new Error(data.error.message));
-          let text = data.candidates?.[0]?.content?.parts?.[0]?.text || '';
-          text = text.replace(/```json\n?/g, '').replace(/```\n?/g, '').trim();
-          resolve(JSON.parse(text));
-        } catch (e) { reject(e); }
-      });
-    });
-    req.on('error', reject);
-    req.write(body);
-    req.end();
-  });
-}
-
-function sleep(ms) { return new Promise(r => setTimeout(r, ms)); }
-
-async function main() {
-  const browser = await puppeteer.launch({ headless: 'new', args: ['--no-sandbox', '--disable-setuid-sandbox'] });
-  const db = new Client({ connectionString: PG_URL });
-  await db.connect();
-  
-  let success = 0, failed = 0;
-  
-  for (const p of ARTE_PRODUCTS) {
-    const page = await browser.newPage();
-    try {
-      console.log(`[arte] Navigating to ${p.sku} — ${p.name}...`);
-      await page.setUserAgent('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36');
-      await page.goto(p.url, { waitUntil: 'networkidle2', timeout: 30000 });
-      await sleep(2000);
-      
-      // Find the packshot/product image on the page
-      const imgSrc = await page.evaluate(() => {
-        // Look for the main product image - Arte uses various selectors
-        const selectors = [
-          'img[src*="Packshot"]',
-          'img[src*="packshot"]', 
-          'img[src*="Flatshot"]',
-          '.product-image img',
-          '.product-detail img',
-          '[class*="product"] img[src*="arte-international"]',
-          'img[src*="edge.arte-international.com"]'
-        ];
-        for (const sel of selectors) {
-          const img = document.querySelector(sel);
-          if (img && img.src) return img.src;
-        }
-        // Fallback: find the largest image on page with arte domain
-        const allImgs = Array.from(document.querySelectorAll('img[src*="arte-international"]'));
-        if (allImgs.length) {
-          return allImgs.sort((a, b) => (b.naturalWidth || 0) - (a.naturalWidth || 0))[0]?.src;
-        }
-        return null;
-      });
-      
-      if (!imgSrc) {
-        // Fallback: take a screenshot of the product area
-        console.log(`  No image found, taking page screenshot...`);
-        const screenshot = await page.screenshot({ type: 'jpeg', quality: 85 });
-        const ai = await callGemini(screenshot.toString('base64'), 'image/jpeg');
-        await saveToDb(db, p, ai);
-        success++;
-        console.log(`  ✅ (screenshot) Colors: ${(ai.colors||[]).join(', ')}`);
-      } else {
-        console.log(`  Found image: ${imgSrc.substring(0, 80)}...`);
-        // Fetch the image through puppeteer's page context (inherits cookies/referer)
-        const imgB64 = await page.evaluate(async (src) => {
-          const resp = await fetch(src);
-          const blob = await resp.blob();
-          return new Promise((resolve) => {
-            const reader = new FileReader();
-            reader.onloadend = () => resolve(reader.result.split(',')[1]);
-            reader.readAsDataURL(blob);
-          });
-        }, imgSrc);
-        
-        const ai = await callGemini(imgB64, 'image/jpeg');
-        await saveToDb(db, p, ai);
-        success++;
-        console.log(`  ✅ Colors: ${(ai.colors||[]).join(', ')} | BG: ${ai.backgroundColor}`);
-      }
-      await sleep(1500);
-    } catch (err) {
-      console.error(`  ❌ FAILED ${p.sku}: ${err.message}`);
-      failed++;
-    } finally {
-      await page.close();
-    }
-  }
-  
-  await browser.close();
-  await db.end();
-  console.log(`\nDone: ${success} enriched, ${failed} failed out of ${ARTE_PRODUCTS.length}`);
-}
-
-async function saveToDb(db, p, ai) {
-  const colorsStr = (ai.colors || []).join(', ');
-  const stylesStr = (ai.styles || []).join(', ');
-  const patternsStr = (ai.patterns || []).join(', ');
-  const tagsStr = (ai.tags || []).join(', ');
-  
-  await db.query(`UPDATE vendor_catalog SET 
-    ai_colors = $1, ai_background_color = $2, ai_styles = $3, 
-    ai_patterns = $4, ai_tags = $5, ai_description = $6 
-    WHERE id = $7`, 
-    [colorsStr, ai.backgroundColor, stylesStr, patternsStr, tagsStr, ai.description, p.vc_id]);
-  
-  await db.query(`UPDATE arte_catalog SET 
-    ai_colors = $1, ai_background_color = $2, ai_styles = $3, 
-    ai_patterns = $4, ai_tags = $5, ai_description = $6, ai_accepted_at = NOW() 
-    WHERE arte_sku = $7`,
-    [JSON.stringify(ai.colors), ai.backgroundColor, JSON.stringify(ai.styles), 
-     JSON.stringify(ai.patterns), JSON.stringify(ai.tags), ai.description, p.sku]);
-}
-
-main().catch(console.error);
diff --git a/enrich-remaining-13.js.pre-scrub-2026-05-07.bak b/enrich-remaining-13.js.pre-scrub-2026-05-07.bak
deleted file mode 100644
index ac73925a..00000000
--- a/enrich-remaining-13.js.pre-scrub-2026-05-07.bak
+++ /dev/null
@@ -1,136 +0,0 @@
-const https = require('https');
-const http = require('http');
-const { Client } = require('pg');
-
-const GEMINI_KEY = 'REDACTED_GEMINI_KEY';
-const GEMINI_MODEL = 'gemini-2.0-flash';
-const PG_URL = 'postgresql://dw_admin:DW2024SecurePass@127.0.0.1:5432/dw_unified';
-
-const PRODUCTS = [
-  // Arte — use packshot_url from arte_catalog
-  { vc_id: 123, vendor: 'arte', sku: '19433', name: 'Horus Desert Sun', img: 'https://edge.arte-international.com/media/a0686a22-83f6-46a5-86fe-b17566b88245/conversions/EssentialsLuxor_Horus_19433_Packshot_Web_LR-medium-two-thirds.jpg' },
-  { vc_id: 16,  vendor: 'arte', sku: '23401', name: 'Amarna Bister', img: 'https://edge.arte-international.com/media/d4610242-66e0-4b12-8723-df90ddb40e04/conversions/Memphis_Amarna_23401_Packshot_Web_HR-medium-two-thirds.jpg' },
-  { vc_id: 15,  vendor: 'arte', sku: '23412', name: 'Sakkara Antique Gold', img: 'https://edge.arte-international.com/media/6fe8a7ec-ecdd-49c4-b398-223293c48eed/conversions/Memphis_Sakkara_23412_Packshot_Web_HR-medium-two-thirds.jpg' },
-  { vc_id: 172, vendor: 'arte', sku: '23413', name: 'Sakkara Off-white', img: 'https://edge.arte-international.com/media/a45badcf-d133-44b1-bcd6-1d74b4f13141/conversions/Memphis_Sakkara_23413_Packshot_Web_HR-medium-two-thirds.jpg' },
-  { vc_id: 72,  vendor: 'arte', sku: '23421', name: 'Ibis Biscuit', img: 'https://edge.arte-international.com/media/fb1738af-4e34-4d36-9a3e-7392f590381a/conversions/Memphis_Ibis_23421_Packshot_Web_LR-medium-two-thirds.jpg' },
-  { vc_id: 75,  vendor: 'arte', sku: '23422', name: 'Ibis Chestnut', img: 'https://edge.arte-international.com/media/2899330e-abba-481e-9098-ffd76f1731cf/conversions/Memphis_Ibis_23422_Packshot_Web_LR-medium-two-thirds.jpg' },
-  { vc_id: 78,  vendor: 'arte', sku: '23431', name: 'Siwa Bone', img: 'https://edge.arte-international.com/media/2ef17968-181d-4da5-8c8a-59f719a3c0fd/conversions/Memphis_Siwa_23431_Packshot_Web_LR-medium-two-thirds.jpg' },
-  { vc_id: 83,  vendor: 'arte', sku: '23433', name: 'Siwa Biscuit', img: 'https://edge.arte-international.com/media/fac5d958-0b0e-437f-abbe-a07a2c147676/conversions/Memphis_Siwa_23433_Packshot_Web_LR-medium-two-thirds.jpg' },
-  { vc_id: 183, vendor: 'arte', sku: '29221', name: 'Galon Grey Linen', img: 'https://edge.arte-international.com/media/f92ad97a-a59b-431c-84ad-0a7520995f6a/conversions/Allures_Galon_29221_Flatshot_Web_LR-medium-two-thirds.jpg' },
-  // Black Edition — use vendor_catalog image_url
-  { vc_id: 1751, vendor: 'black_edition', sku: 'W924/01', name: 'Mizumi Panel Carbon', img: 'https://static.theromogroup.com/rb/cache/image/720x720/catalog/product/W/9/W924-01FP-mizumi-panel-charcoal_00.jpg' },
-  { vc_id: 1752, vendor: 'black_edition', sku: 'W924/02', name: 'Mizumi Panel Midnight', img: 'https://static.theromogroup.com/rb/cache/image/720x720/catalog/product/W/9/W924-02FP-mizumi-panel-midnight_00.jpg' },
-  { vc_id: 1753, vendor: 'black_edition', sku: 'W924/03', name: 'Mizumi Panel Basalt', img: 'https://static.theromogroup.com/rb/cache/image/720x720/catalog/product/W/9/W924-03FP-mizumi-panel-stone_00.jpg' },
-  { vc_id: 1754, vendor: 'black_edition', sku: 'W924/04', name: 'Mizumi Panel Viridian', img: 'https://static.theromogroup.com/rb/cache/image/720x720/catalog/product/W/9/W924-04FP-mizumi-panel-teal_02.jpg' },
-];
-
-const PROMPT = `You are an interior design color analyst for luxury wallcoverings.
-
-Analyze this wallcovering image and return a JSON object with these fields:
-- "colors": array of 3-6 prominent colors visible (e.g. ["Gold", "Ivory", "Charcoal"])
-- "backgroundColor": the single dominant background color (e.g. "Ivory")
-- "styles": array of 1-3 design styles (e.g. ["Contemporary", "Art Deco"])
-- "patterns": array of 1-2 pattern types (e.g. ["Geometric", "Textured"])
-- "tags": array of 4-8 interior design tags (e.g. ["luxury", "metallic", "hospitality", "accent wall"])
-- "description": a 2-sentence commercial description for an interior designer audience. NEVER use the word "wallpaper" — always say "wallcovering".
-
-Return ONLY the JSON object, no markdown fences, no explanation.`;
-
-function fetchImageBase64(url) {
-  return new Promise((resolve, reject) => {
-    const mod = url.startsWith('https') ? https : http;
-    mod.get(url, { headers: { 'User-Agent': 'Mozilla/5.0' } }, (res) => {
-      if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
-        return fetchImageBase64(res.headers.location).then(resolve).catch(reject);
-      }
-      if (res.statusCode !== 200) return reject(new Error(`HTTP ${res.statusCode} for ${url}`));
-      const chunks = [];
-      res.on('data', c => chunks.push(c));
-      res.on('end', () => resolve(Buffer.concat(chunks).toString('base64')));
-      res.on('error', reject);
-    }).on('error', reject);
-  });
-}
-
-function callGemini(imageBase64, mimeType) {
-  return new Promise((resolve, reject) => {
-    const body = JSON.stringify({
-      contents: [{
-        parts: [
-          { text: PROMPT },
-          { inline_data: { mime_type: mimeType || 'image/jpeg', data: imageBase64 } }
-        ]
-      }],
-      generationConfig: { temperature: 0.3, maxOutputTokens: 1024 }
-    });
-    const url = new URL(`https://generativelanguage.googleapis.com/v1beta/models/${GEMINI_MODEL}:generateContent?key=${GEMINI_KEY}`);
-    const opts = { method: 'POST', hostname: url.hostname, path: url.pathname + url.search, headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(body) } };
-    const req = https.request(opts, (res) => {
-      const chunks = [];
-      res.on('data', c => chunks.push(c));
-      res.on('end', () => {
-        try {
-          const data = JSON.parse(Buffer.concat(chunks).toString());
-          if (data.error) return reject(new Error(data.error.message));
-          let text = data.candidates?.[0]?.content?.parts?.[0]?.text || '';
-          text = text.replace(/```json\n?/g, '').replace(/```\n?/g, '').trim();
-          resolve(JSON.parse(text));
-        } catch (e) { reject(e); }
-      });
-    });
-    req.on('error', reject);
-    req.write(body);
-    req.end();
-  });
-}
-
-function sleep(ms) { return new Promise(r => setTimeout(r, ms)); }
-
-async function main() {
-  const db = new Client({ connectionString: PG_URL });
-  await db.connect();
-  
-  let success = 0, failed = 0;
-  
-  for (const p of PRODUCTS) {
-    try {
-      console.log(`[${p.vendor}] Analyzing ${p.sku} — ${p.name}...`);
-      const imgB64 = await fetchImageBase64(p.img);
-      const ai = await callGemini(imgB64);
-      
-      const colorsStr = (ai.colors || []).join(', ');
-      const stylesStr = (ai.styles || []).join(', ');
-      const patternsStr = (ai.patterns || []).join(', ');
-      const tagsStr = (ai.tags || []).join(', ');
-      
-      // Update vendor_catalog
-      await db.query(`UPDATE vendor_catalog SET 
-        ai_colors = $1, ai_background_color = $2, ai_styles = $3, 
-        ai_patterns = $4, ai_tags = $5, ai_description = $6 
-        WHERE id = $7`, 
-        [colorsStr, ai.backgroundColor, stylesStr, patternsStr, tagsStr, ai.description, p.vc_id]);
-      
-      // Also update arte_catalog if Arte
-      if (p.vendor === 'arte') {
-        await db.query(`UPDATE arte_catalog SET 
-          ai_colors = $1, ai_background_color = $2, ai_styles = $3, 
-          ai_patterns = $4, ai_tags = $5, ai_description = $6, ai_accepted_at = NOW() 
-          WHERE arte_sku = $7`,
-          [JSON.stringify(ai.colors), ai.backgroundColor, JSON.stringify(ai.styles), 
-           JSON.stringify(ai.patterns), JSON.stringify(ai.tags), ai.description, p.sku]);
-      }
-      
-      console.log(`  ✅ Colors: ${colorsStr} | BG: ${ai.backgroundColor}`);
-      success++;
-      await sleep(1200); // rate limit
-    } catch (err) {
-      console.error(`  ❌ FAILED ${p.sku}: ${err.message}`);
-      failed++;
-    }
-  }
-  
-  await db.end();
-  console.log(`\nDone: ${success} enriched, ${failed} failed out of ${PRODUCTS.length}`);
-}
-
-main().catch(console.error);
diff --git a/gemini-classify-texture.js.pre-scrub-2026-05-07.bak b/gemini-classify-texture.js.pre-scrub-2026-05-07.bak
deleted file mode 100644
index 6d0f7fab..00000000
--- a/gemini-classify-texture.js.pre-scrub-2026-05-07.bak
+++ /dev/null
@@ -1,112 +0,0 @@
-#!/usr/bin/env node
-const { Client } = require('pg');
-const https = require('https');
-const http = require('http');
-
-const GEMINI_KEY = 'REDACTED_GEMINI_KEY';
-const GEMINI_URL = `https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key=${GEMINI_KEY}`;
-const PROMPT = `Is this wallcovering image a repeating pattern or a non-repeating texture? If it's a texture (grasscloth, linen, plain, solid, stucco, metallic, stone, concrete, weave, etc.), respond 'TEXTURE'. If it has a visible repeating pattern (florals, stripes, damask, geometric, medallion, toile, scenic, mural, etc.), respond 'PATTERN'. Respond with only one word: TEXTURE or PATTERN.`;
-
-const db = new Client({ connectionString: 'postgresql://dw_admin:DW2024SecurePass@127.0.0.1:5432/dw_unified' });
-const products = require('/tmp/thibaut_remaining.json');
-const stats = { texture: 0, pattern: 0, error: 0, total: products.length };
-
-function sleep(ms) { return new Promise(r => setTimeout(r, ms)); }
-
-function downloadImage(url) {
-  return new Promise((resolve, reject) => {
-    const request = (targetUrl, redirectCount = 0) => {
-      if (redirectCount > 5) return reject(new Error('Too many redirects'));
-      const client = targetUrl.startsWith('https') ? https : http;
-      client.get(targetUrl, { timeout: 15000 }, (res) => {
-        if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
-          return request(res.headers.location, redirectCount + 1);
-        }
-        if (res.statusCode !== 200) { res.resume(); return reject(new Error(`HTTP ${res.statusCode}`)); }
-        const chunks = [];
-        res.on('data', chunk => chunks.push(chunk));
-        res.on('end', () => {
-          const buf = Buffer.concat(chunks);
-          const ct = res.headers['content-type'] || 'image/jpeg';
-          resolve({ base64: buf.toString('base64'), mimeType: ct.split(';')[0].trim() });
-        });
-        res.on('error', reject);
-      }).on('error', reject).on('timeout', function() { this.destroy(); reject(new Error('timeout')); });
-    };
-    request(url);
-  });
-}
-
-function classifyWithGemini(base64, mimeType) {
-  const payload = {
-    contents: [{ parts: [
-      { text: PROMPT },
-      { inline_data: { mime_type: mimeType, data: base64 } }
-    ]}],
-    generationConfig: { temperature: 0.1, maxOutputTokens: 10 }
-  };
-  return new Promise((resolve, reject) => {
-    const url = new URL(GEMINI_URL);
-    const postData = JSON.stringify(payload);
-    const req = https.request({
-      hostname: url.hostname, path: url.pathname + url.search, method: 'POST',
-      headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(postData) },
-      timeout: 30000
-    }, (res) => {
-      let data = '';
-      res.on('data', chunk => data += chunk);
-      res.on('end', () => {
-        try {
-          const json = JSON.parse(data);
-          if (json.error) return reject(new Error(json.error.message));
-          const text = json.candidates?.[0]?.content?.parts?.[0]?.text?.trim().toUpperCase() || '';
-          if (text.includes('TEXTURE')) resolve('TEXTURE');
-          else if (text.includes('PATTERN')) resolve('PATTERN');
-          else resolve('UNKNOWN');
-        } catch (e) { reject(e); }
-      });
-    });
-    req.on('error', reject);
-    req.on('timeout', () => { req.destroy(); reject(new Error('gemini timeout')); });
-    req.write(postData); req.end();
-  });
-}
-
-async function run() {
-  await db.connect();
-  console.log(`Classifying ${products.length} thibaut products with Gemini Vision (base64)...`);
-  const startTime = Date.now();
-
-  for (let i = 0; i < products.length; i++) {
-    const p = products[i];
-    try {
-      const { base64, mimeType } = await downloadImage(p.image_url);
-      const result = await classifyWithGemini(base64, mimeType);
-
-      if (result === 'TEXTURE') {
-        await db.query(`UPDATE thibaut_catalog SET repeat_h = '0', match_type = COALESCE(match_type, 'Texture'), updated_at = NOW() WHERE id = $1`, [p.id]);
-        stats.texture++;
-      } else if (result === 'PATTERN') {
-        await db.query(`UPDATE thibaut_catalog SET match_type = COALESCE(match_type, 'Unknown Pattern'), updated_at = NOW() WHERE id = $1`, [p.id]);
-        stats.pattern++;
-      } else { stats.error++; }
-
-      const pct = ((i + 1) / products.length * 100).toFixed(0);
-      if ((i + 1) % 5 === 0 || i === products.length - 1)
-        console.log(`  [${pct}%] ${i + 1}/${products.length} — T:${stats.texture} P:${stats.pattern} E:${stats.error} | ${p.pattern_name} → ${result}`);
-      await sleep(200);
-    } catch (err) {
-      stats.error++;
-      console.log(`  [ERR] #${p.id} ${p.pattern_name}: ${err.message.substring(0, 80)}`);
-      await sleep(500);
-    }
-  }
-
-  const elapsed = ((Date.now() - startTime) / 1000).toFixed(0);
-  console.log(`\n=== GEMINI CLASSIFICATION COMPLETE (${elapsed}s) ===`);
-  console.log(`Total: ${stats.total} | Texture: ${stats.texture} | Pattern: ${stats.pattern} | Errors: ${stats.error}`);
-  await db.end();
-  require('fs').writeFileSync('/tmp/gemini_classify_stats.json', JSON.stringify(stats));
-}
-
-run().catch(err => { console.error('Fatal:', err); process.exit(1); });
diff --git a/gemini-enrich-arte.js.pre-scrub-2026-05-07.bak b/gemini-enrich-arte.js.pre-scrub-2026-05-07.bak
deleted file mode 100644
index 3c281f6b..00000000
--- a/gemini-enrich-arte.js.pre-scrub-2026-05-07.bak
+++ /dev/null
@@ -1,120 +0,0 @@
-const https = require('https');
-const { Client } = require('pg');
-
-const GEMINI_KEY = 'REDACTED_GEMINI_KEY';
-const GEMINI_URL = `https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key=${GEMINI_KEY}`;
-
-const products = [
-  { id: 123, sku: '19433', name: 'Horus Desert Sun', img: 'https://edge.arte-international.com/media/4960d9f1-bc0a-4bcb-9a82-c09d9e5e7d98/conversions/EssentialsLuxor_Horus_19436_Roomshot_Web_LR-medium-two-thirds.jpg' },
-  { id: 16, sku: '23401', name: 'Amarna Bister', img: 'https://edge.arte-international.com/media/cc56d30c-5e1c-4b22-878e-b437681e6f1b/conversions/Memphis_Amarna_23400_Roomshot_Web_LR-medium-two-thirds.jpg' },
-  { id: 15, sku: '23412', name: 'Sakkara Antique Gold', img: 'https://edge.arte-international.com/media/a580214f-25ff-472e-b88c-6a2bd8dfab64/conversions/Memphis_Sakkara_23410_Roomshot_Web_LR-medium-two-thirds.jpg' },
-  { id: 172, sku: '23413', name: 'Sakkara Off-white', img: 'https://edge.arte-international.com/media/a580214f-25ff-472e-b88c-6a2bd8dfab64/conversions/Memphis_Sakkara_23410_Roomshot_Web_LR-medium-two-thirds.jpg' },
-  { id: 72, sku: '23421', name: 'Ibis Biscuit', img: 'https://edge.arte-international.com/media/2183e2bc-0620-43c4-81ae-6364abed3937/conversions/Memphis_Ibis_23420_Roomshot_Web_LR-medium-two-thirds.jpg' },
-  { id: 75, sku: '23422', name: 'Ibis Chestnut', img: 'https://edge.arte-international.com/media/2183e2bc-0620-43c4-81ae-6364abed3937/conversions/Memphis_Ibis_23420_Roomshot_Web_LR-medium-two-thirds.jpg' },
-  { id: 78, sku: '23431', name: 'Siwa Bone', img: 'https://edge.arte-international.com/media/428898de-d288-40f3-b463-1714c59de939/conversions/Memphis_Siwa_23432_Roomshot_Web_LR-medium-two-thirds.jpg' },
-  { id: 83, sku: '23433', name: 'Siwa Biscuit', img: 'https://edge.arte-international.com/media/428898de-d288-40f3-b463-1714c59de939/conversions/Memphis_Siwa_23432_Roomshot_Web_LR-medium-two-thirds.jpg' },
-  { id: 183, sku: '29221', name: 'Galon Grey Linen', img: 'https://edge.arte-international.com/media/8571da7a-7bf9-4abc-adf0-76863cee5413/conversions/Allures_Galon9224_1_Roomshot_Web_LR-medium-two-thirds.jpg' },
-];
-
-function downloadImage(url) {
-  return new Promise((resolve, reject) => {
-    const get = (u, redirects = 0) => {
-      if (redirects > 5) return reject(new Error('Too many redirects'));
-      https.get(u, { headers: { 'User-Agent': 'Mozilla/5.0' } }, res => {
-        if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
-          return get(res.headers.location, redirects + 1);
-        }
-        if (res.statusCode !== 200) return reject(new Error(`HTTP ${res.statusCode}`));
-        const chunks = [];
-        res.on('data', c => chunks.push(c));
-        res.on('end', () => resolve(Buffer.concat(chunks)));
-      }).on('error', reject);
-    };
-    get(url);
-  });
-}
-
-function callGemini(body) {
-  return new Promise((resolve, reject) => {
-    const data = JSON.stringify(body);
-    const req = https.request(GEMINI_URL, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(data) } }, res => {
-      let buf = '';
-      res.on('data', c => buf += c);
-      res.on('end', () => { try { resolve(JSON.parse(buf)); } catch(e) { reject(new Error(buf.slice(0,500))); } });
-    });
-    req.on('error', reject);
-    req.write(data);
-    req.end();
-  });
-}
-
-const PROMPT = `You are an interior design wallcovering analyst. Analyze this wallcovering image and return ONLY valid JSON (no markdown, no backticks):
-{
-  "colors": ["<list 3-6 dominant colors you actually see>"],
-  "backgroundColor": "<the single dominant background color>",
-  "styles": ["<1-3 design styles>"],
-  "patterns": ["<1-3 pattern types>"],
-  "tags": ["<5-10 interior design tags>"],
-  "description": "<2-sentence commercial description>"
-}
-The product is: PRODUCT_NAME. Focus on what you actually see.`;
-
-async function analyzeOne(product) {
-  console.log(`  Downloading image...`);
-  const imgBuf = await downloadImage(product.img);
-  console.log(`  Downloaded ${(imgBuf.length/1024).toFixed(0)}KB, calling Gemini...`);
-  const b64 = imgBuf.toString('base64');
-  
-  const body = {
-    contents: [{
-      parts: [
-        { text: PROMPT.replace('PRODUCT_NAME', product.name) },
-        { inlineData: { mimeType: 'image/jpeg', data: b64 } }
-      ]
-    }],
-    generationConfig: { temperature: 0.2 }
-  };
-  
-  const res = await callGemini(body);
-  if (res.error) throw new Error(res.error.message);
-  const text = res.candidates?.[0]?.content?.parts?.[0]?.text || '';
-  return JSON.parse(text.replace(/```json\n?/g, '').replace(/```\n?/g, '').trim());
-}
-
-async function main() {
-  const client = new Client({ connectionString: 'postgresql://dw_admin:DW2024SecurePass@127.0.0.1:5432/dw_unified' });
-  await client.connect();
-  console.log('Processing 9 Arte products with base64 image download...\n');
-  
-  let success = 0, failed = 0;
-  
-  for (const p of products) {
-    console.log(`[${success+failed+1}/9] ${p.sku} (${p.name})`);
-    try {
-      const a = await analyzeOne(p);
-      const colors = a.colors?.join(', ') || '';
-      const bg = a.backgroundColor || '';
-      const styles = a.styles?.join(', ') || '';
-      const patterns = a.patterns?.join(', ') || '';
-      const tags = a.tags?.join(', ') || '';
-      const desc = a.description || '';
-      
-      await client.query(`UPDATE vendor_catalog SET ai_colors=$1, ai_background_color=$2, ai_styles=$3, ai_patterns=$4, ai_tags=$5, ai_description=$6 WHERE id=$7`,
-        [colors, bg, styles, patterns, tags, desc, p.id]);
-      await client.query(`UPDATE arte_catalog SET ai_colors=$1, ai_background_color=$2, ai_styles=$3, ai_patterns=$4, ai_tags=$5, ai_description=$6 WHERE arte_sku=$7`,
-        [colors, bg, styles, patterns, tags, desc, p.sku]);
-      
-      console.log(`  ✅ Colors: ${colors}`);
-      success++;
-    } catch(e) {
-      console.log(`  ❌ ${e.message}`);
-      failed++;
-    }
-    await new Promise(r => setTimeout(r, 300));
-  }
-  
-  console.log(`\nDone: ${success} succeeded, ${failed} failed`);
-  await client.end();
-}
-
-main().catch(e => { console.error(e); process.exit(1); });
diff --git a/gemini-enrich-arte9.js.pre-scrub-2026-05-07.bak b/gemini-enrich-arte9.js.pre-scrub-2026-05-07.bak
deleted file mode 100644
index 40f41186..00000000
--- a/gemini-enrich-arte9.js.pre-scrub-2026-05-07.bak
+++ /dev/null
@@ -1,152 +0,0 @@
-const https = require('https');
-const { Client } = require('pg');
-
-const GEMINI_KEY = 'REDACTED_GEMINI_KEY';
-const GEMINI_URL = `https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key=${GEMINI_KEY}`;
-
-const db = new Client({ connectionString: 'postgresql://dw_admin:DW2024SecurePass@127.0.0.1:5432/dw_unified' });
-
-const PROMPT = `You are an interior design wallcovering analyst. Analyze this wallcovering product image (it may be a room setting showing the wallcovering installed on walls).
-Focus on the WALLCOVERING itself, not furniture or decor.
-Return ONLY valid JSON with these fields:
-{
-  "colors": ["<list of 3-6 dominant colors in the wallcovering, e.g. Gold, Cream, Charcoal>"],
-  "backgroundColor": "<the single dominant background color of the wallcovering>",
-  "tags": ["<5-8 interior design tags, e.g. Luxury, Textured, Organic, Hospitality>"],
-  "styles": ["<1-3 design style periods, e.g. Contemporary, Art Deco, Mid-Century Modern>"],
-  "patterns": ["<1-3 pattern types, e.g. Geometric, Abstract, Textural, Striped>"],
-  "description": "<2 sentence commercial description for an architect or interior designer>"
-}
-No markdown, no code fences, just raw JSON.`;
-
-// Use vendor_catalog room shot URLs (confirmed working 200 OK)
-const products = [
-  { id: 123, sku: '19433', name: 'Horus Desert Sun', img: 'https://edge.arte-international.com/media/4960d9f1-bc0a-4bcb-9a82-c09d9e5e7d98/conversions/EssentialsLuxor_Horus_19436_Roomshot_Web_LR-medium-two-thirds.jpg' },
-  { id: 16, sku: '23401', name: 'Amarna Bister', img: 'https://edge.arte-international.com/media/cc56d30c-5e1c-4b22-878e-b437681e6f1b/conversions/Memphis_Amarna_23400_Roomshot_Web_LR-medium-two-thirds.jpg' },
-  { id: 15, sku: '23412', name: 'Sakkara Antique Gold', img: 'https://edge.arte-international.com/media/a580214f-25ff-472e-b88c-6a2bd8dfab64/conversions/Memphis_Sakkara_23410_Roomshot_Web_LR-medium-two-thirds.jpg' },
-  { id: 172, sku: '23413', name: 'Sakkara Off-white', img: 'https://edge.arte-international.com/media/a580214f-25ff-472e-b88c-6a2bd8dfab64/conversions/Memphis_Sakkara_23410_Roomshot_Web_LR-medium-two-thirds.jpg' },
-  { id: 72, sku: '23421', name: 'Ibis Biscuit', img: 'https://edge.arte-international.com/media/2183e2bc-0620-43c4-81ae-6364abed3937/conversions/Memphis_Ibis_23420_Roomshot_Web_LR-medium-two-thirds.jpg' },
-  { id: 75, sku: '23422', name: 'Ibis Chestnut', img: 'https://edge.arte-international.com/media/2183e2bc-0620-43c4-81ae-6364abed3937/conversions/Memphis_Ibis_23420_Roomshot_Web_LR-medium-two-thirds.jpg' },
-  { id: 78, sku: '23431', name: 'Siwa Bone', img: 'https://edge.arte-international.com/media/428898de-d288-40f3-b463-1714c59de939/conversions/Memphis_Siwa_23432_Roomshot_Web_LR-medium-two-thirds.jpg' },
-  { id: 83, sku: '23433', name: 'Siwa Biscuit', img: 'https://edge.arte-international.com/media/428898de-d288-40f3-b463-1714c59de939/conversions/Memphis_Siwa_23432_Roomshot_Web_LR-medium-two-thirds.jpg' },
-  { id: 183, sku: '29221', name: 'Galon Grey Linen', img: 'https://edge.arte-international.com/media/8571da7a-7bf9-4abc-adf0-76863cee5413/conversions/Allures_Galon9224_1_Roomshot_Web_LR-medium-two-thirds.jpg' },
-];
-
-function fetchImageAsBase64(url) {
-  return new Promise((resolve, reject) => {
-    https.get(url, { headers: { 'User-Agent': 'Mozilla/5.0' } }, res => {
-      if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
-        return fetchImageAsBase64(res.headers.location).then(resolve).catch(reject);
-      }
-      if (res.statusCode !== 200) {
-        return reject(new Error(`HTTP ${res.statusCode} for ${url}`));
-      }
-      const chunks = [];
-      res.on('data', c => chunks.push(c));
-      res.on('end', () => {
-        const buf = Buffer.concat(chunks);
-        if (buf.length < 1000) return reject(new Error(`Image too small: ${buf.length} bytes`));
-        resolve(buf.toString('base64'));
-      });
-      res.on('error', reject);
-    }).on('error', reject);
-  });
-}
-
-function callGemini(b64, productName) {
-  return new Promise((resolve, reject) => {
-    const payload = JSON.stringify({
-      contents: [{
-        parts: [
-          { text: `Product: ${productName}\n\n${PROMPT}` },
-          { inline_data: { mime_type: 'image/jpeg', data: b64 } }
-        ]
-      }]
-    });
-    
-    const url = new URL(GEMINI_URL);
-    const options = {
-      hostname: url.hostname,
-      path: url.pathname + url.search,
-      method: 'POST',
-      headers: { 'Content-Type': 'application/json' }
-    };
-    
-    const req = https.request(options, res => {
-      let data = '';
-      res.on('data', chunk => data += chunk);
-      res.on('end', () => {
-        try {
-          const json = JSON.parse(data);
-          if (json.error) return reject(new Error(json.error.message));
-          const text = json.candidates?.[0]?.content?.parts?.[0]?.text || '';
-          const cleaned = text.replace(/```json\n?/g, '').replace(/```\n?/g, '').trim();
-          resolve(JSON.parse(cleaned));
-        } catch (e) {
-          reject(new Error(`Parse: ${e.message} — ${data.substring(0, 200)}`));
-        }
-      });
-    });
-    req.on('error', reject);
-    req.write(payload);
-    req.end();
-  });
-}
-
-async function updateDB(product, analysis) {
-  // vendor_catalog uses text columns — postgres array literal
-  const pgArr = arr => `{${arr.map(c => `"${c.replace(/"/g, '')}"`).join(',')}}`;
-  const vc_colors = pgArr(analysis.colors || []);
-  const vc_tags = pgArr(analysis.tags || []);
-  const vc_styles = pgArr(analysis.styles || []);
-  const vc_patterns = pgArr(analysis.patterns || []);
-
-  // arte_catalog uses jsonb columns — JSON array
-  const ac_colors = JSON.stringify(analysis.colors || []);
-  const ac_tags = JSON.stringify(analysis.tags || []);
-  const ac_styles = JSON.stringify(analysis.styles || []);
-  const ac_patterns = JSON.stringify(analysis.patterns || []);
-
-  // Update vendor_catalog (text columns)
-  await db.query(`
-    UPDATE vendor_catalog SET
-      ai_colors = $1, ai_background_color = $2, ai_tags = $3,
-      ai_styles = $4, ai_patterns = $5, ai_description = $6
-    WHERE id = $7
-  `, [vc_colors, analysis.backgroundColor, vc_tags, vc_styles, vc_patterns, analysis.description, product.id]);
-
-  // Update arte_catalog (jsonb columns)
-  await db.query(`
-    UPDATE arte_catalog SET
-      ai_colors = $1::jsonb, ai_background_color = $2, ai_tags = $3::jsonb,
-      ai_styles = $4::jsonb, ai_patterns = $5::jsonb, ai_description = $6
-    WHERE arte_sku = $7
-  `, [ac_colors, analysis.backgroundColor, ac_tags, ac_styles, ac_patterns, analysis.description, product.sku]);
-}
-
-async function main() {
-  await db.connect();
-  console.log('Connected. Processing 9 Arte products...\n');
-  let ok = 0, fail = 0;
-  
-  for (const p of products) {
-    try {
-      process.stdout.write(`${p.sku} ${p.name}... `);
-      const b64 = await fetchImageAsBase64(p.img);
-      console.log(`img ${Math.round(b64.length/1024)}KB`);
-      const analysis = await callGemini(b64, p.name);
-      console.log(`  ✅ ${analysis.colors.join(', ')} | bg: ${analysis.backgroundColor}`);
-      await updateDB(p, analysis);
-      ok++;
-      await new Promise(r => setTimeout(r, 1000));
-    } catch (e) {
-      console.log(`  ❌ ${e.message}`);
-      fail++;
-    }
-  }
-  
-  console.log(`\nDone: ${ok}/9 succeeded, ${fail} failed`);
-  await db.end();
-}
-
-main().catch(e => { console.error(e); process.exit(1); });
diff --git a/gemini-enrich-gaps.js.pre-scrub-2026-05-07.bak b/gemini-enrich-gaps.js.pre-scrub-2026-05-07.bak
deleted file mode 100644
index e43977c3..00000000
--- a/gemini-enrich-gaps.js.pre-scrub-2026-05-07.bak
+++ /dev/null
@@ -1,175 +0,0 @@
-const https = require('https');
-const http = require('http');
-const { Client } = require('pg');
-
-const GEMINI_KEY = 'REDACTED_GEMINI_KEY';
-const GEMINI_MODEL = 'gemini-2.0-flash';
-const GEMINI_URL = `https://generativelanguage.googleapis.com/v1beta/models/${GEMINI_MODEL}:generateContent?key=${GEMINI_KEY}`;
-
-const DB = 'postgresql://dw_admin:DW2024SecurePass@127.0.0.1:5432/dw_unified';
-
-// Products to process — using arte_catalog packshot images for Arte, vendor_catalog images for Black Edition
-const products = [
-  // Arte (9) — packshot images from arte_catalog
-  { id: 123, vendor: 'arte', sku: '19433', name: 'Horus Desert Sun', img: 'https://edge.arte-international.com/media/a0686a22-83f6-46a5-86fe-b17566b88245/conversions/EssentialsLuxor_Horus_19433_Packshot_Web_LR-thumb-two-thirds.jpg' },
-  { id: 16,  vendor: 'arte', sku: '23401', name: 'Amarna Bister', img: 'https://edge.arte-international.com/media/d4610242-66e0-4b12-8723-df90ddb40e04/conversions/Memphis_Amarna_23401_Packshot_Web_HR-thumb-two-thirds.jpg' },
-  { id: 15,  vendor: 'arte', sku: '23412', name: 'Sakkara Antique Gold', img: 'https://edge.arte-international.com/media/6fe8a7ec-ecdd-49c4-b398-223293c48eed/conversions/Memphis_Sakkara_23412_Packshot_Web_HR-thumb-two-thirds.jpg' },
-  { id: 172, vendor: 'arte', sku: '23413', name: 'Sakkara Off-white', img: 'https://edge.arte-international.com/media/a45badcf-d133-44b1-bcd6-1d74b4f13141/conversions/Memphis_Sakkara_23413_Packshot_Web_HR-thumb-two-thirds.jpg' },
-  { id: 72,  vendor: 'arte', sku: '23421', name: 'Ibis Biscuit', img: 'https://edge.arte-international.com/media/fb1738af-4e34-4d36-9a3e-7392f590381a/conversions/Memphis_Ibis_23421_Packshot_Web_LR-thumb-two-thirds.jpg' },
-  { id: 75,  vendor: 'arte', sku: '23422', name: 'Ibis Chestnut', img: 'https://edge.arte-international.com/media/2899330e-abba-481e-9098-ffd76f1731cf/conversions/Memphis_Ibis_23422_Packshot_Web_LR-thumb-two-thirds.jpg' },
-  { id: 78,  vendor: 'arte', sku: '23431', name: 'Siwa Bone', img: 'https://edge.arte-international.com/media/2ef17968-181d-4da5-8c8a-59f719a3c0fd/conversions/Memphis_Siwa_23431_Packshot_Web_LR-thumb-two-thirds.jpg' },
-  { id: 83,  vendor: 'arte', sku: '23433', name: 'Siwa Biscuit', img: 'https://edge.arte-international.com/media/fac5d958-0b0e-437f-abbe-a07a2c147676/conversions/Memphis_Siwa_23433_Packshot_Web_LR-thumb-two-thirds.jpg' },
-  { id: 183, vendor: 'arte', sku: '29221', name: 'Galon Grey Linen', img: 'https://edge.arte-international.com/media/f92ad97a-a59b-431c-84ad-0a7520995f6a/conversions/Allures_Galon_29221_Flatshot_Web_LR-thumb-two-thirds.jpg' },
-  // Black Edition (4)
-  { id: 1751, vendor: 'black_edition', sku: 'W924/01', name: 'Mizumi Panel Carbon', img: 'https://static.theromogroup.com/rb/cache/image/720x720/catalog/product/W/9/W924-01FP-mizumi-panel-charcoal_00.jpg' },
-  { id: 1752, vendor: 'black_edition', sku: 'W924/02', name: 'Mizumi Panel Midnight', img: 'https://static.theromogroup.com/rb/cache/image/720x720/catalog/product/W/9/W924-02FP-mizumi-panel-midnight_00.jpg' },
-  { id: 1753, vendor: 'black_edition', sku: 'W924/03', name: 'Mizumi Panel Basalt', img: 'https://static.theromogroup.com/rb/cache/image/720x720/catalog/product/W/9/W924-03FP-mizumi-panel-stone_00.jpg' },
-  { id: 1754, vendor: 'black_edition', sku: 'W924/04', name: 'Mizumi Panel Viridian', img: 'https://static.theromogroup.com/rb/cache/image/720x720/catalog/product/W/9/W924-04FP-mizumi-panel-teal_02.jpg' },
-];
-
-const PROMPT = `You are an interior design wallcovering analyst. Analyze this wallcovering product image and return ONLY a JSON object (no markdown, no code fences) with these fields:
-
-{
-  "colors": ["<list 3-6 dominant colors you actually see, e.g. Warm Gold, Ivory, Charcoal>"],
-  "backgroundColor": "<the single most dominant background color>",
-  "styles": ["<1-3 design styles, e.g. Contemporary, Art Deco, Mid-Century Modern>"],
-  "patterns": ["<1-3 pattern types, e.g. Geometric, Textured, Floral, Abstract>"],
-  "tags": ["<5-10 interior design tags for search, e.g. luxury, metallic, neutral, organic, minimalist>"],
-  "description": "<2 sentences describing the wallcovering for a commercial buyer>"
-}
-
-Product: PRODUCT_NAME
-Return ONLY the JSON object.`;
-
-function fetchJSON(url, body) {
-  return new Promise((resolve, reject) => {
-    const data = JSON.stringify(body);
-    const parsed = new URL(url);
-    const req = https.request({
-      hostname: parsed.hostname,
-      path: parsed.pathname + parsed.search,
-      method: 'POST',
-      headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(data) }
-    }, (res) => {
-      let chunks = [];
-      res.on('data', c => chunks.push(c));
-      res.on('end', () => {
-        try { resolve(JSON.parse(Buffer.concat(chunks).toString())); }
-        catch(e) { reject(new Error('JSON parse error: ' + Buffer.concat(chunks).toString().slice(0, 500))); }
-      });
-    });
-    req.on('error', reject);
-    req.write(data);
-    req.end();
-  });
-}
-
-async function analyzeWithGemini(product) {
-  const prompt = PROMPT.replace('PRODUCT_NAME', product.name);
-  const body = {
-    contents: [{
-      parts: [
-        { text: prompt },
-        { inlineData: undefined },
-        { fileData: undefined }
-      ]
-    }],
-    generationConfig: { temperature: 0.2, maxOutputTokens: 1024 }
-  };
-  // Use image URL reference
-  body.contents[0].parts = [
-    { text: prompt },
-    { text: `Image URL for reference: ${product.img}` }
-  ];
-  
-  // Actually fetch the image and send inline
-  const imgBuf = await fetchImage(product.img);
-  if (imgBuf) {
-    body.contents[0].parts = [
-      { text: prompt },
-      { inlineData: { mimeType: 'image/jpeg', data: imgBuf.toString('base64') } }
-    ];
-  }
-
-  const resp = await fetchJSON(GEMINI_URL, body);
-  if (resp.error) throw new Error(resp.error.message);
-  
-  const text = resp.candidates?.[0]?.content?.parts?.[0]?.text || '';
-  // Strip markdown fences if present
-  const clean = text.replace(/```json\s*/g, '').replace(/```\s*/g, '').trim();
-  return JSON.parse(clean);
-}
-
-function fetchImage(url) {
-  return new Promise((resolve) => {
-    const mod = url.startsWith('https') ? https : http;
-    mod.get(url, { headers: { 'User-Agent': 'Mozilla/5.0' } }, (res) => {
-      if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
-        return fetchImage(res.headers.location).then(resolve);
-      }
-      if (res.statusCode !== 200) { resolve(null); return; }
-      let chunks = [];
-      res.on('data', c => chunks.push(c));
-      res.on('end', () => resolve(Buffer.concat(chunks)));
-    }).on('error', () => resolve(null));
-  });
-}
-
-async function main() {
-  const db = new Client({ connectionString: DB });
-  await db.connect();
-  
-  let success = 0, failed = 0;
-  
-  for (const p of products) {
-    try {
-      console.log(`[${p.vendor}] Analyzing ${p.sku} - ${p.name}...`);
-      const ai = await analyzeWithGemini(p);
-      
-      const colorsStr = (ai.colors || []).join(', ');
-      const tagsStr = (ai.tags || []).join(', ');
-      const stylesStr = (ai.styles || []).join(', ');
-      const patternsStr = (ai.patterns || []).join(', ');
-      
-      // Update vendor_catalog
-      await db.query(`
-        UPDATE vendor_catalog SET
-          ai_colors = $1,
-          ai_background_color = $2,
-          ai_styles = $3,
-          ai_patterns = $4,
-          ai_tags = $5,
-          ai_description = $6
-        WHERE id = $7
-      `, [colorsStr, ai.backgroundColor, stylesStr, patternsStr, tagsStr, ai.description, p.id]);
-      
-      // Also update arte_catalog if Arte vendor
-      if (p.vendor === 'arte') {
-        await db.query(`
-          UPDATE arte_catalog SET
-            ai_colors = $1,
-            ai_background_color = $2,
-            ai_styles = $3,
-            ai_patterns = $4,
-            ai_tags = $5,
-            ai_description = $6
-          WHERE arte_sku = $7
-        `, [colorsStr, ai.backgroundColor, stylesStr, patternsStr, tagsStr, ai.description, p.sku]);
-      }
-      
-      console.log(`  ✅ ${p.sku}: colors=[${colorsStr}] bg=${ai.backgroundColor}`);
-      success++;
-      
-      // Rate limit: ~300ms between calls
-      await new Promise(r => setTimeout(r, 300));
-    } catch (err) {
-      console.error(`  ❌ ${p.sku}: ${err.message}`);
-      failed++;
-    }
-  }
-  
-  console.log(`\nDone: ${success} succeeded, ${failed} failed out of ${products.length}`);
-  await db.end();
-}
-
-main().catch(e => { console.error(e); process.exit(1); });
diff --git a/scripts/ncw-cleanup/run-full-20260506-095239.log b/scripts/ncw-cleanup/run-full-20260506-095239.log
deleted file mode 100644
index 2230ce0e..00000000
--- a/scripts/ncw-cleanup/run-full-20260506-095239.log
+++ /dev/null
@@ -1,77 +0,0 @@
-# NCW Cleanup — connected to Designer Wallcoverings and Fabrics (designer-laboratory-sandbox.myshopify.com)
-# Mode: execute
-# Started: 2026-05-06T16:52:39.407Z
-
-## Plan
-- Archive (dupes)              : 227
-- Add Color: tag (live-checked): 318 EUR products to inspect
-- Normalize mfr_sku to NCW####-##: 304 EUR products
-
-## Archiving 227 duplicates
-  archived 10/227
-  archived 20/227
-  archived 30/227
-  archived 40/227
-  archived 50/227
-  archived 60/227
-  archived 70/227
-  archived 80/227
-  archived 90/227
-  archived 100/227
-  archived 110/227
-  archived 120/227
-  archived 130/227
-  archived 140/227
-  archived 150/227
-  archived 160/227
-  archived 170/227
-  archived 180/227
-  archived 190/227
-  archived 200/227
-  archived 210/227
-  archived 220/227
-
-## Checking live tags + adding Color: where missing on 318 EUR products
-  progress: tagged=22 already=3 skipped=0
-  progress: tagged=47 already=3 skipped=0
-  progress: tagged=64 already=11 skipped=0
-  ? no color in: EUR-80189 → "Pamir 03 - Neutral Wallcovering | Nina Campbell"
-  ? no color in: EUR-80191 → "Pamir 05 - Goldenrod Wallcovering | Nina Campbell"
-  ? no color in: EUR-80203 → "Mahayana 06 - Onyx Wallcovering | Nina Campbell"
-  ? no color in: EUR-80210 → "Khitan 07 | Nina Campbell Europe"
-  ? no color in: EUR-80216 → "Keightley's Folio 01 - Pastel Wallcovering | Nina Campbell"
-  ? no color in: EUR-80255 → "Fontibre 02 - Warm Neutral Wallcovering | Nina Campbell"
-  ? no color in: EUR-80270 → "Pavilion Garden 04 - Onyx Wallcovering | Nina Campbell"
-  ? no color in: EUR-80281 → "Palmetto 05 - Warm Neutral Wallcovering | Nina Campbell"
-  progress: tagged=123 already=19 skipped=8
-  ? no color in: EUR-80293 → "Meredith 03 - Taupe Wallcovering | Nina Campbell"
-  progress: tagged=142 already=24 skipped=9
-  progress: tagged=167 already=24 skipped=9
-  ? no color in: EUR-80351 → "Baville 01 - Neutral Wallcovering | Nina Campbell"
-  ? no color in: EUR-80360 → "Bonnelles Diamond 04 - Bisque Wallcovering | Nina Campbell"
-  progress: tagged=190 already=24 skipped=11
-  ? no color in: EUR-80384 → "Fortoiseau 05 - Onyx Wallcovering | Nina Campbell"
-  progress: tagged=214 already=24 skipped=12
-  ? no color in: EUR-80414 → "Kingsley Fans 04 - Neutral Wallcovering | Nina Campbell"
-  progress: tagged=238 already=24 skipped=13
-  ? no color in: EUR-80444 → "Meridor Stripe 04 - Neutral Wallcovering | Nina Campbell"
-  progress: tagged=262 already=24 skipped=14
-  ? no color in: EUR-80455 → "Plumier Stripe 05 - Neutral Wallcovering | Nina Campbell"
-  tag pass complete: tagged=279 already_had=24 skipped=15 errors=0
-
-## Normalizing mfr_sku → NCW####-## for 304 EUR products
-  mfr progress: fixed=13 already=37
-  mfr progress: fixed=33 already=67
-  mfr progress: fixed=43 already=82
-  mfr progress: fixed=68 already=82
-  mfr progress: fixed=81 already=94
-  mfr progress: fixed=96 already=104
-  mfr progress: fixed=112 already=113
-  mfr progress: fixed=152 already=148
-  mfr pass complete: fixed=156 already_correct=148 errors=0
-
-## Done
-  archived OK  : 227/227  (errors: 0)
-  tagged OK    : 279/318  (skipped: 15, errors: 0)
-  mfr fixed    : 156/304  (already_correct: 148, errors: 0)
-  audit log    : /Users/stevestudio2/Projects/Designer-Wallcoverings/scripts/ncw-cleanup/audit-execute-2026-05-06T17-08-22.json
diff --git a/scripts/ncw-cleanup/run-tick2-20260506-100853.log b/scripts/ncw-cleanup/run-tick2-20260506-100853.log
deleted file mode 100644
index 1cf58126..00000000
--- a/scripts/ncw-cleanup/run-tick2-20260506-100853.log
+++ /dev/null
@@ -1,48 +0,0 @@
-# NCW Cleanup — connected to Designer Wallcoverings and Fabrics (designer-laboratory-sandbox.myshopify.com)
-# Mode: execute
-# Started: 2026-05-06T17:08:53.362Z
-
-## Plan
-- Archive (dupes)              : 227
-- Add Color: tag (live-checked): 318 EUR products to inspect
-- Normalize mfr_sku to NCW####-##: 148 EUR products
-
-## Archiving 227 duplicates
-  archived 10/227
-  archived 20/227
-  archived 30/227
-  archived 40/227
-  archived 50/227
-  archived 60/227
-  archived 70/227
-  archived 80/227
-  archived 90/227
-  archived 100/227
-  archived 110/227
-  archived 120/227
-  archived 130/227
-  archived 140/227
-  archived 150/227
-  archived 160/227
-  archived 170/227
-  archived 180/227
-  archived 190/227
-  archived 200/227
-  archived 210/227
-  archived 220/227
-
-## Checking live tags + adding Color: where missing on 318 EUR products
-  ? no color in: EUR-80191 → "Pamir 05 - Goldenrod Wallcovering | Nina Campbell"
-  ? no color in: EUR-80210 → "Khitan 07 | Nina Campbell Europe"
-  ? no color in: EUR-80216 → "Keightley's Folio 01 - Pastel Wallcovering | Nina Campbell"
-  progress: tagged=3 already=119 skipped=3
-  tag pass complete: tagged=12 already_had=303 skipped=3 errors=0
-
-## Normalizing mfr_sku → NCW####-## for 148 EUR products
-  mfr pass complete: fixed=0 already_correct=148 errors=0
-
-## Done
-  archived OK  : 227/227  (errors: 0)
-  tagged OK    : 12/318  (skipped: 3, errors: 0)
-  mfr fixed    : 0/148  (already_correct: 148, errors: 0)
-  audit log    : /Users/stevestudio2/Projects/Designer-Wallcoverings/scripts/ncw-cleanup/audit-execute-2026-05-06T17-14-02.json

← f19be762 security: strip hardcoded dw_admin DSN password -> env-first  ·  back to Designer Wallcoverings  ·  security: strip hardcoded PGPASSWORD/pm2/shell-string secret d287f9d6 →