[object Object]

← back to Flockedwallpaper

code-health: untrack test/diagnostic html + 404-guard scratch/test/backup/source files in static server

5234c733fb1682e80161dcf61537a1108e85ce02 · 2026-06-12 09:21:58 -0700 · Steve

Files touched

Diff

commit 5234c733fb1682e80161dcf61537a1108e85ce02
Author: Steve <steve@designerwallcoverings.com>
Date:   Fri Jun 12 09:21:58 2026 -0700

    code-health: untrack test/diagnostic html + 404-guard scratch/test/backup/source files in static server
---
 .gitignore       |   29 +-
 diagnostic.html  |   78 ----
 server.js        |   11 +
 simple-test.html | 1041 ------------------------------------------------------
 4 files changed, 28 insertions(+), 1131 deletions(-)

diff --git a/.gitignore b/.gitignore
index 0c093e1..f48a964 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,18 +1,23 @@
-node_modules/
-.env*
-tmp/
-*.log
-.DS_Store
-dist/
-build/
-.next/
+*-backup*
+*-backup.json
+*-desktop-backup*
+*-old-backup*
+*-test.html
 *.bak
 *.bak.*
+*.log
 *.pre-*
 *.zip
-*-old-backup*
-*-backup*
-*-backup.json
+.DS_Store
+.env*
+.next/
+build/
+diagnostic.html
+dist/
 index-fresh.html
-*-desktop-backup*
+index-old-backup.html
+node_modules/
+server-old-backup.js
+simple-test.html
 test-*.html
+tmp/
diff --git a/diagnostic.html b/diagnostic.html
deleted file mode 100644
index a9850ce..0000000
--- a/diagnostic.html
+++ /dev/null
@@ -1,78 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-    <meta charset="UTF-8">
-    <title>Diagnostic Page</title>
-    <style>
-        body { font-family: monospace; padding: 20px; background: #f0f0f0; }
-        .test { margin: 20px 0; padding: 15px; background: white; border-left: 4px solid #4CAF50; }
-        .pass { border-color: #4CAF50; }
-        .fail { border-color: #f44336; }
-        h1 { color: #333; }
-        .info { background: #e3f2fd; padding: 10px; margin: 10px 0; }
-    </style>
-</head>
-<body>
-    <h1>FlockedWallpaper Diagnostic</h1>
-    <div class="info">
-        <strong>Time:</strong> <span id="timestamp"></span><br>
-        <strong>User Agent:</strong> <span id="userAgent"></span>
-    </div>
-
-    <div class="test" id="test1">
-        <strong>Test 1:</strong> JavaScript is running
-    </div>
-
-    <div class="test" id="test2">
-        <strong>Test 2:</strong> Checking JSON file...
-    </div>
-
-    <div class="test" id="test3">
-        <strong>Test 3:</strong> DOM Ready
-    </div>
-
-    <div id="results"></div>
-
-    <script>
-        // Test 1: Basic JS
-        document.getElementById('timestamp').textContent = new Date().toISOString();
-        document.getElementById('userAgent').textContent = navigator.userAgent;
-        document.getElementById('test1').classList.add('pass');
-        document.getElementById('test1').innerHTML += ' <span style="color: green;">✓ PASS</span>';
-
-        // Test 2: Fetch JSON
-        fetch('flocked-products.json?v=' + Date.now())
-            .then(res => {
-                console.log('Fetch response:', res.status);
-                if (!res.ok) throw new Error('HTTP ' + res.status);
-                return res.json();
-            })
-            .then(data => {
-                console.log('Products:', data.length);
-                const test2 = document.getElementById('test2');
-                test2.classList.add('pass');
-                test2.innerHTML = `<strong>Test 2:</strong> JSON loaded successfully - ${data.length} products <span style="color: green;">✓ PASS</span>`;
-
-                // Show first 3 products
-                const results = document.getElementById('results');
-                results.innerHTML = '<h2>Sample Products:</h2>';
-                data.slice(0, 3).forEach(p => {
-                    results.innerHTML += `<div class="info"><strong>${p.title}</strong><br>SKU: ${p.primarySku}<br>Color: ${p.primaryColor}</div>`;
-                });
-            })
-            .catch(err => {
-                console.error('Fetch error:', err);
-                const test2 = document.getElementById('test2');
-                test2.classList.add('fail');
-                test2.innerHTML = `<strong>Test 2:</strong> JSON fetch failed - ${err.message} <span style="color: red;">✗ FAIL</span>`;
-            });
-
-        // Test 3: DOM Ready
-        document.addEventListener('DOMContentLoaded', () => {
-            const test3 = document.getElementById('test3');
-            test3.classList.add('pass');
-            test3.innerHTML = '<strong>Test 3:</strong> DOMContentLoaded fired <span style="color: green;">✓ PASS</span>';
-        });
-    </script>
-</body>
-</html>
diff --git a/server.js b/server.js
index ae461c8..f13043d 100644
--- a/server.js
+++ b/server.js
@@ -83,6 +83,17 @@ const server = http.createServer((req, res) => {
         return;
     }
 
+    // Never serve scratch / test / backup / server-source files even if present on disk
+    // (index-old-backup.html, simple-test.html, diagnostic.html, server-old-backup.js, etc.).
+    // Return 404 so their existence isn't revealed.
+    const baseName = path.basename(filePath);
+    if (/\.(bak|orig|save|old|swp)$|\.backup\.json$|\.pre-|-backup\.|-old\.|^test-|-test\.|^simple-test|^diagnostic|^test-load|^server(-|\.js$|-old)/i.test(baseName) ||
+        /^package(-lock)?\.json$/i.test(baseName)) {
+        res.writeHead(404, { 'Content-Type': 'text/html' });
+        res.end('<h1>404 - File Not Found</h1>', 'utf-8');
+        return;
+    }
+
     const extname = String(path.extname(filePath)).toLowerCase();
     const contentType = mimeTypes[extname] || 'application/octet-stream';
 
diff --git a/simple-test.html b/simple-test.html
deleted file mode 100644
index cb11126..0000000
--- a/simple-test.html
+++ /dev/null
@@ -1,1041 +0,0 @@
-<!DOCTYPE html>
-<html lang="en">
-<head>
-    <meta charset="UTF-8">
-    <meta name="viewport" content="width=device-width, initial-scale=1.0">
-    <meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
-    <title>All Flocked Wallpapers | FlockedWallpaper.com</title>
-    <meta name="description" content="Browse our complete collection of luxury flocked and velvet wallcoverings. Filter by color, search by name or SKU.">
-    <link rel="preconnect" href="https://fonts.googleapis.com">
-    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
-    <link href="https://fonts.googleapis.com/css2?family=Cormorant+Garamond:ital,wght@0,400;0,600;0,700;1,400&family=Outfit:wght@300;400;500;600&display=swap" rel="stylesheet">
-    <style>
-        *, *::before, *::after {
-            margin: 0;
-            padding: 0;
-            box-sizing: border-box;
-        }
-
-        :root {
-            --plum: #6B3769;
-            --plum-light: #8B4789;
-            --plum-dark: #4A2548;
-            --velvet: #2C1A2E;
-            --cream: #FAF7F5;
-            --warm-white: #FFF9F5;
-            --gold-mist: #C9A96E;
-            --text-primary: #1A1018;
-            --text-secondary: #6B5E6E;
-            --text-muted: #9E929F;
-            --border: #E8DFE8;
-            --card-shadow: 0 2px 12px rgba(107, 55, 105, 0.08);
-            --card-hover: 0 8px 32px rgba(107, 55, 105, 0.16);
-            --radius: 6px;
-            --font-display: 'Cormorant Garamond', Georgia, serif;
-            --font-body: 'Outfit', -apple-system, sans-serif;
-        }
-
-        html {
-            scroll-behavior: smooth;
-            -webkit-text-size-adjust: 100%;
-        }
-
-        body {
-            font-family: var(--font-body);
-            background: var(--cream);
-            color: var(--text-primary);
-            line-height: 1.5;
-            min-height: 100vh;
-        }
-
-        /* Sticky Header */
-        .site-header {
-            position: sticky;
-            top: 0;
-            z-index: 100;
-            background: rgba(255, 255, 255, 0.92);
-            backdrop-filter: blur(16px);
-            -webkit-backdrop-filter: blur(16px);
-            border-bottom: 1px solid var(--border);
-            transition: box-shadow 0.3s ease;
-        }
-
-        .site-header.scrolled {
-            box-shadow: 0 4px 20px rgba(107, 55, 105, 0.1);
-        }
-
-        .header-inner {
-            max-width: 1440px;
-            margin: 0 auto;
-            padding: 14px 16px;
-            display: flex;
-            align-items: center;
-            justify-content: space-between;
-            gap: 12px;
-        }
-
-        .brand {
-            text-decoration: none;
-            color: inherit;
-            flex-shrink: 0;
-        }
-
-        .brand-name {
-            font-family: var(--font-display);
-            font-size: 20px;
-            font-weight: 700;
-            letter-spacing: 0.3px;
-            color: var(--velvet);
-        }
-
-        .brand-tag {
-            font-size: 9px;
-            font-weight: 400;
-            letter-spacing: 2.5px;
-            text-transform: uppercase;
-            color: var(--text-muted);
-            margin-top: 1px;
-        }
-
-        .header-search {
-            flex: 1;
-            max-width: 360px;
-            position: relative;
-        }
-
-        .header-search input {
-            width: 100%;
-            padding: 10px 14px 10px 38px;
-            border: 1px solid var(--border);
-            border-radius: 40px;
-            font-family: var(--font-body);
-            font-size: 14px;
-            font-weight: 400;
-            color: var(--text-primary);
-            background: var(--cream);
-            outline: none;
-            transition: border-color 0.2s, box-shadow 0.2s;
-        }
-
-        .header-search input::placeholder {
-            color: var(--text-muted);
-        }
-
-        .header-search input:focus {
-            border-color: var(--plum-light);
-            box-shadow: 0 0 0 3px rgba(139, 71, 137, 0.1);
-        }
-
-        .search-icon {
-            position: absolute;
-            left: 14px;
-            top: 50%;
-            transform: translateY(-50%);
-            width: 16px;
-            height: 16px;
-            color: var(--text-muted);
-            pointer-events: none;
-        }
-
-        .header-count {
-            font-size: 12px;
-            font-weight: 500;
-            color: var(--text-secondary);
-            letter-spacing: 0.5px;
-            white-space: nowrap;
-            flex-shrink: 0;
-        }
-
-        .header-count strong {
-            color: var(--plum);
-            font-weight: 600;
-        }
-
-        /* Hero Banner */
-        .hero-banner {
-            background: var(--velvet);
-            padding: 48px 16px 40px;
-            text-align: center;
-            position: relative;
-            overflow: hidden;
-        }
-
-        .hero-banner::before {
-            content: '';
-            position: absolute;
-            inset: 0;
-            background:
-                radial-gradient(ellipse at 20% 50%, rgba(139, 71, 137, 0.15) 0%, transparent 60%),
-                radial-gradient(ellipse at 80% 50%, rgba(201, 169, 110, 0.1) 0%, transparent 60%);
-            pointer-events: none;
-        }
-
-        .hero-banner::after {
-            content: '';
-            position: absolute;
-            inset: 0;
-            background: url("data:image/svg+xml,%3Csvg width='60' height='60' viewBox='0 0 60 60' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='none' fill-rule='evenodd'%3E%3Cg fill='%23ffffff' fill-opacity='0.03'%3E%3Cpath d='M36 34v-4h-2v4h-4v2h4v4h2v-4h4v-2h-4zm0-30V0h-2v4h-4v2h4v4h2V6h4V4h-4zM6 34v-4H4v4H0v2h4v4h2v-4h4v-2H6zM6 4V0H4v4H0v2h4v4h2V6h4V4H6z'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E");
-            pointer-events: none;
-        }
-
-        .hero-banner h1 {
-            font-family: var(--font-display);
-            font-size: 32px;
-            font-weight: 700;
-            color: #fff;
-            letter-spacing: 0.5px;
-            margin-bottom: 8px;
-            position: relative;
-        }
-
-        .hero-banner p {
-            font-size: 14px;
-            font-weight: 300;
-            color: rgba(255, 255, 255, 0.6);
-            max-width: 480px;
-            margin: 0 auto;
-            line-height: 1.6;
-            position: relative;
-        }
-
-        /* Color Filter Bar */
-        .filter-bar {
-            background: #fff;
-            border-bottom: 1px solid var(--border);
-            padding: 12px 0;
-            overflow: hidden;
-        }
-
-        .filter-scroll {
-            display: flex;
-            gap: 8px;
-            padding: 0 16px;
-            overflow-x: auto;
-            -webkit-overflow-scrolling: touch;
-            scrollbar-width: none;
-        }
-
-        .filter-scroll::-webkit-scrollbar {
-            display: none;
-        }
-
-        .color-pill {
-            display: inline-flex;
-            align-items: center;
-            gap: 6px;
-            padding: 7px 14px;
-            border: 1.5px solid var(--border);
-            border-radius: 40px;
-            font-family: var(--font-body);
-            font-size: 12px;
-            font-weight: 500;
-            color: var(--text-secondary);
-            background: #fff;
-            cursor: pointer;
-            white-space: nowrap;
-            transition: all 0.2s ease;
-            -webkit-tap-highlight-color: transparent;
-            user-select: none;
-            flex-shrink: 0;
-        }
-
-        .color-pill:hover {
-            border-color: var(--plum-light);
-            color: var(--plum);
-        }
-
-        .color-pill:active {
-            transform: scale(0.96);
-        }
-
-        .color-pill.active {
-            background: var(--plum);
-            border-color: var(--plum);
-            color: #fff;
-        }
-
-        .color-pill.active .pill-dot {
-            border-color: rgba(255, 255, 255, 0.4);
-        }
-
-        .pill-dot {
-            width: 12px;
-            height: 12px;
-            border-radius: 50%;
-            border: 1px solid rgba(0, 0, 0, 0.1);
-            flex-shrink: 0;
-        }
-
-        .pill-count {
-            font-size: 11px;
-            opacity: 0.6;
-        }
-
-        /* Results Summary */
-        .results-bar {
-            padding: 14px 16px;
-            display: flex;
-            align-items: center;
-            justify-content: space-between;
-            max-width: 1440px;
-            margin: 0 auto;
-        }
-
-        .results-text {
-            font-size: 13px;
-            color: var(--text-muted);
-            font-weight: 400;
-        }
-
-        .results-text strong {
-            color: var(--text-primary);
-            font-weight: 600;
-        }
-
-        .sort-toggle {
-            font-family: var(--font-body);
-            font-size: 12px;
-            font-weight: 500;
-            color: var(--text-secondary);
-            background: none;
-            border: 1px solid var(--border);
-            border-radius: 4px;
-            padding: 6px 12px;
-            cursor: pointer;
-            transition: all 0.2s;
-        }
-
-        .sort-toggle:hover {
-            border-color: var(--plum-light);
-            color: var(--plum);
-        }
-
-        /* Product Grid */
-        .grid-container {
-            max-width: 1440px;
-            margin: 0 auto;
-            padding: 0 12px 60px;
-        }
-
-        .product-grid {
-            display: grid;
-            grid-template-columns: repeat(2, 1fr);
-            gap: 10px;
-        }
-
-        .product-card {
-            background: #fff;
-            border-radius: var(--radius);
-            overflow: hidden;
-            box-shadow: var(--card-shadow);
-            cursor: pointer;
-            transition: transform 0.25s ease, box-shadow 0.25s ease;
-            -webkit-tap-highlight-color: transparent;
-            opacity: 0;
-            transform: translateY(12px);
-            animation: cardReveal 0.4s ease forwards;
-        }
-
-        @keyframes cardReveal {
-            to {
-                opacity: 1;
-                transform: translateY(0);
-            }
-        }
-
-        .product-card:hover {
-            transform: translateY(-3px);
-            box-shadow: var(--card-hover);
-        }
-
-        .card-img-wrap {
-            position: relative;
-            width: 100%;
-            padding-top: 120%;
-            overflow: hidden;
-            background: linear-gradient(135deg, #f3edf3 0%, #ece4ec 100%);
-        }
-
-        .card-img-wrap img {
-            position: absolute;
-            inset: 0;
-            width: 100%;
-            height: 100%;
-            object-fit: cover;
-            transition: transform 0.4s ease;
-        }
-
-        .product-card:hover .card-img-wrap img {
-            transform: scale(1.04);
-        }
-
-        .card-img-wrap.loading::after {
-            content: '';
-            position: absolute;
-            inset: 0;
-            background: linear-gradient(90deg, transparent 25%, rgba(255,255,255,0.4) 50%, transparent 75%);
-            background-size: 200% 100%;
-            animation: shimmer 1.4s infinite;
-        }
-
-        @keyframes shimmer {
-            from { background-position: 200% 0; }
-            to { background-position: -200% 0; }
-        }
-
-        .card-color-dot {
-            position: absolute;
-            top: 8px;
-            right: 8px;
-            width: 18px;
-            height: 18px;
-            border-radius: 50%;
-            border: 2px solid rgba(255, 255, 255, 0.8);
-            box-shadow: 0 1px 4px rgba(0,0,0,0.2);
-        }
-
-        .card-vendor {
-            position: absolute;
-            bottom: 0;
-            left: 0;
-            right: 0;
-            padding: 24px 10px 8px;
-            background: linear-gradient(transparent, rgba(0,0,0,0.5));
-            font-size: 10px;
-            font-weight: 500;
-            letter-spacing: 1.5px;
-            text-transform: uppercase;
-            color: rgba(255, 255, 255, 0.85);
-        }
-
-        .card-body {
-            padding: 10px 10px 12px;
-        }
-
-        .card-sku {
-            font-size: 10px;
-            font-weight: 500;
-            letter-spacing: 1px;
-            text-transform: uppercase;
-            color: var(--text-muted);
-            margin-bottom: 3px;
-        }
-
-        .card-title {
-            font-family: var(--font-display);
-            font-size: 15px;
-            font-weight: 600;
-            color: var(--text-primary);
-            line-height: 1.3;
-            display: -webkit-box;
-            -webkit-line-clamp: 2;
-            -webkit-box-orient: vertical;
-            overflow: hidden;
-            min-height: 39px;
-        }
-
-        .card-meta {
-            display: flex;
-            align-items: center;
-            justify-content: space-between;
-            margin-top: 8px;
-            padding-top: 8px;
-            border-top: 1px solid var(--border);
-        }
-
-        .card-price {
-            font-size: 13px;
-            font-weight: 600;
-            color: var(--plum);
-        }
-
-        .card-cta {
-            font-size: 11px;
-            font-weight: 500;
-            color: var(--gold-mist);
-            letter-spacing: 0.3px;
-        }
-
-        /* Infinite Scroll Loader */
-        .scroll-loader {
-            text-align: center;
-            padding: 40px 16px;
-            color: var(--text-muted);
-            font-size: 13px;
-        }
-
-        .scroll-loader .dots {
-            display: inline-flex;
-            gap: 4px;
-        }
-
-        .scroll-loader .dots span {
-            width: 6px;
-            height: 6px;
-            border-radius: 50%;
-            background: var(--plum-light);
-            animation: dotPulse 1.2s ease infinite;
-        }
-
-        .scroll-loader .dots span:nth-child(2) { animation-delay: 0.2s; }
-        .scroll-loader .dots span:nth-child(3) { animation-delay: 0.4s; }
-
-        @keyframes dotPulse {
-            0%, 80%, 100% { opacity: 0.2; transform: scale(0.8); }
-            40% { opacity: 1; transform: scale(1); }
-        }
-
-        /* Back to Top */
-        .back-to-top {
-            position: fixed;
-            bottom: 20px;
-            right: 20px;
-            width: 44px;
-            height: 44px;
-            border-radius: 50%;
-            background: var(--plum);
-            color: #fff;
-            border: none;
-            cursor: pointer;
-            display: flex;
-            align-items: center;
-            justify-content: center;
-            box-shadow: 0 4px 16px rgba(107, 55, 105, 0.3);
-            opacity: 0;
-            transform: translateY(20px);
-            transition: opacity 0.3s, transform 0.3s;
-            z-index: 90;
-            -webkit-tap-highlight-color: transparent;
-        }
-
-        .back-to-top.visible {
-            opacity: 1;
-            transform: translateY(0);
-        }
-
-        .back-to-top:active {
-            transform: scale(0.92);
-        }
-
-        /* Empty State */
-        .empty-state {
-            grid-column: 1 / -1;
-            text-align: center;
-            padding: 60px 20px;
-        }
-
-        .empty-state h3 {
-            font-family: var(--font-display);
-            font-size: 24px;
-            color: var(--text-secondary);
-            margin-bottom: 8px;
-        }
-
-        .empty-state p {
-            font-size: 14px;
-            color: var(--text-muted);
-        }
-
-        .empty-state button {
-            margin-top: 16px;
-            padding: 10px 24px;
-            border: 1.5px solid var(--plum);
-            border-radius: 40px;
-            background: none;
-            color: var(--plum);
-            font-family: var(--font-body);
-            font-size: 13px;
-            font-weight: 500;
-            cursor: pointer;
-            transition: all 0.2s;
-        }
-
-        .empty-state button:hover {
-            background: var(--plum);
-            color: #fff;
-        }
-
-        /* Footer */
-        .site-footer {
-            background: var(--velvet);
-            padding: 32px 16px;
-            text-align: center;
-        }
-
-        .site-footer p {
-            font-size: 12px;
-            color: rgba(255, 255, 255, 0.4);
-            letter-spacing: 0.5px;
-        }
-
-        .site-footer a {
-            color: var(--gold-mist);
-            text-decoration: none;
-        }
-
-        /* Tablet (600px+) */
-        @media (min-width: 600px) {
-            .header-inner {
-                padding: 16px 24px;
-            }
-
-            .brand-name { font-size: 24px; }
-
-            .hero-banner {
-                padding: 64px 24px 56px;
-            }
-
-            .hero-banner h1 { font-size: 42px; }
-            .hero-banner p { font-size: 15px; }
-
-            .filter-scroll { padding: 0 24px; }
-
-            .grid-container { padding: 0 20px 80px; }
-
-            .product-grid {
-                grid-template-columns: repeat(3, 1fr);
-                gap: 16px;
-            }
-
-            .card-body { padding: 12px 14px 14px; }
-            .card-title { font-size: 16px; }
-        }
-
-        /* Desktop (960px+) */
-        @media (min-width: 960px) {
-            .header-inner { padding: 18px 40px; }
-
-            .brand-name { font-size: 28px; }
-            .brand-tag { font-size: 10px; }
-
-            .hero-banner {
-                padding: 80px 40px 68px;
-            }
-
-            .hero-banner h1 { font-size: 52px; }
-            .hero-banner p { font-size: 16px; max-width: 560px; }
-
-            .filter-scroll {
-                padding: 0 40px;
-                gap: 10px;
-            }
-
-            .color-pill {
-                padding: 8px 18px;
-                font-size: 13px;
-            }
-
-            .results-bar { padding: 16px 40px; }
-
-            .grid-container { padding: 0 36px 100px; }
-
-            .product-grid {
-                grid-template-columns: repeat(4, 1fr);
-                gap: 20px;
-            }
-
-            .card-img-wrap { padding-top: 130%; }
-            .card-body { padding: 14px 16px 16px; }
-            .card-title { font-size: 17px; min-height: 44px; }
-
-            .product-card:hover {
-                transform: translateY(-5px);
-            }
-        }
-
-        /* Wide Desktop (1280px+) */
-        @media (min-width: 1280px) {
-            .product-grid {
-                grid-template-columns: repeat(5, 1fr);
-                gap: 22px;
-            }
-        }
-    </style>
-</head>
-<body>
-
-<!-- Header -->
-<header class="site-header" id="siteHeader">
-    <div class="header-inner">
-        <a href="/" class="brand">
-            <div class="brand-name">FlockedWallpaper.com</div>
-            <div class="brand-tag">Luxury Velvet Wallcoverings</div>
-        </a>
-        <div class="header-search">
-            <svg class="search-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/></svg>
-            <input type="text" id="searchInput" placeholder="Search name, SKU, or color..." autocomplete="off">
-        </div>
-        <div class="header-count" id="headerCount"></div>
-    </div>
-</header>
-
-<!-- Hero -->
-<section class="hero-banner">
-    <h1>The Complete Collection</h1>
-    <p>Every flocked and velvet wallcovering we carry &mdash; browse, filter, and find your perfect texture.</p>
-</section>
-
-<!-- Color Filters -->
-<div class="filter-bar">
-    <div class="filter-scroll" id="filterScroll"></div>
-</div>
-
-<!-- Results -->
-<div class="results-bar">
-    <div class="results-text" id="resultsText"></div>
-    <button class="sort-toggle" id="sortToggle" title="Toggle sort order">Sort: Color</button>
-</div>
-
-<!-- Product Grid -->
-<div class="grid-container">
-    <div class="product-grid" id="productGrid">
-        <div class="scroll-loader" style="grid-column:1/-1;">
-            <div class="dots"><span></span><span></span><span></span></div>
-            <div style="margin-top:12px;">Loading flocked wallpapers...</div>
-        </div>
-    </div>
-    <div class="scroll-loader" id="scrollLoader" style="display:none;">
-        <div class="dots"><span></span><span></span><span></span></div>
-    </div>
-</div>
-
-<!-- Footer -->
-<footer class="site-footer">
-    <p>&copy; 2025 <a href="/">FlockedWallpaper.com</a> &mdash; Part of the Designer Wallcoverings Collection</p>
-</footer>
-
-<!-- Back to Top -->
-<button class="back-to-top" id="backToTop" aria-label="Back to top">
-    <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"><path d="M12 19V5"/><path d="m5 12 7-7 7 7"/></svg>
-</button>
-
-<script>
-(function() {
-    'use strict';
-
-    var allProducts = [];
-    var filtered = [];
-    var displayed = 0;
-    var activeColor = null;
-    var searchQuery = '';
-    var sortMode = 'color';
-    var BATCH = 30;
-
-    var COLORS = {
-        Black:'#1A1A1A', White:'#F5F5F5', Red:'#C0392B', Blue:'#3B6DB5',
-        Green:'#3A7D44', Gold:'#C9A96E', Silver:'#A8A8A8', Pink:'#D4708A',
-        Purple:'#7D4E8A', Beige:'#D4C5A9', Cream:'#F0E8D8', Gray:'#808080',
-        Grey:'#808080', Brown:'#7B5B3A', Yellow:'#D4A843', Orange:'#D47735',
-        Burgundy:'#6E1A2C', Navy:'#1E3A5F', Aqua:'#4EB8B0', Teal:'#2A7B72',
-        Ivory:'#F5F0E1', Charcoal:'#3A3A3A', Neutral:'#B5AA9A'
-    };
-
-    var grid = document.getElementById('productGrid');
-    var filterScroll = document.getElementById('filterScroll');
-    var resultsText = document.getElementById('resultsText');
-    var headerCount = document.getElementById('headerCount');
-    var searchInput = document.getElementById('searchInput');
-    var scrollLoader = document.getElementById('scrollLoader');
-    var sortToggle = document.getElementById('sortToggle');
-    var backToTop = document.getElementById('backToTop');
-    var siteHeader = document.getElementById('siteHeader');
-
-    function init() {
-        var xhr = new XMLHttpRequest();
-        xhr.open('GET', 'flocked-products.json?v=' + Date.now());
-        xhr.onload = function() {
-            if (xhr.status >= 200 && xhr.status < 300) {
-                allProducts = JSON.parse(xhr.responseText);
-                buildFilters();
-                applyFilters();
-            } else {
-                grid.innerHTML = '<div class="empty-state"><h3>Could not load products</h3><p>HTTP ' + xhr.status + '</p></div>';
-            }
-        };
-        xhr.onerror = function() {
-            grid.innerHTML = '<div class="empty-state"><h3>Network error</h3><p>Please refresh the page.</p></div>';
-        };
-        xhr.send();
-    }
-
-    function productColor(p) {
-        if (p.primaryColor && COLORS[p.primaryColor]) return p.primaryColor;
-        if (p.tags) {
-            for (var i = 0; i < p.tags.length; i++) {
-                var keys = Object.keys(COLORS);
-                for (var j = 0; j < keys.length; j++) {
-                    if (p.tags[i].toLowerCase().indexOf(keys[j].toLowerCase()) !== -1) return keys[j];
-                }
-            }
-        }
-        return null;
-    }
-
-    function productHex(p) {
-        var c = productColor(p);
-        return c ? COLORS[c] : '#999';
-    }
-
-    function hexHSL(hex) {
-        var m = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
-        if (!m) return [0,0,0];
-        var r = parseInt(m[1],16)/255, g = parseInt(m[2],16)/255, b = parseInt(m[3],16)/255;
-        var mx = Math.max(r,g,b), mn = Math.min(r,g,b);
-        var h = 0, s = 0, l = (mx+mn)/2;
-        if (mx !== mn) {
-            var d = mx-mn;
-            s = l>0.5 ? d/(2-mx-mn) : d/(mx+mn);
-            if (mx === r) h = ((g-b)/d+(g<b?6:0))/6;
-            else if (mx === g) h = ((b-r)/d+2)/6;
-            else h = ((r-g)/d+4)/6;
-        }
-        return [h*360, s*100, l*100];
-    }
-
-    function sortProducts(arr) {
-        var copy = arr.slice();
-        if (sortMode === 'color') {
-            copy.sort(function(a,b) {
-                var ha = hexHSL(productHex(a)), hb = hexHSL(productHex(b));
-                if (Math.abs(ha[2]-hb[2]) > 12) return ha[2]-hb[2];
-                return ha[0]-hb[0];
-            });
-        } else if (sortMode === 'name') {
-            copy.sort(function(a,b) { return a.title.localeCompare(b.title); });
-        } else {
-            copy.sort(function(a,b) { return new Date(b.createdAt) - new Date(a.createdAt); });
-        }
-        return copy;
-    }
-
-    function buildFilters() {
-        var counts = {};
-        allProducts.forEach(function(p) {
-            var c = productColor(p);
-            if (c) counts[c] = (counts[c]||0) + 1;
-        });
-
-        filterScroll.innerHTML = '';
-        filterScroll.appendChild(makePill('All', null, allProducts.length, true));
-
-        var entries = Object.keys(counts).map(function(k) { return [k, counts[k]]; });
-        entries.sort(function(a,b) { return b[1]-a[1]; });
-        entries.forEach(function(e) {
-            filterScroll.appendChild(makePill(e[0], COLORS[e[0]], e[1], false));
-        });
-    }
-
-    function makePill(label, hex, count, isActive) {
-        var el = document.createElement('button');
-        el.className = 'color-pill' + (isActive ? ' active' : '');
-        el.innerHTML = (hex ? '<span class="pill-dot" style="background:'+hex+'"></span>' : '') +
-            escHTML(label) + ' <span class="pill-count">'+count+'</span>';
-        el.addEventListener('click', function() {
-            activeColor = (label === 'All') ? null : label;
-            var pills = document.querySelectorAll('.color-pill');
-            for (var i = 0; i < pills.length; i++) pills[i].classList.remove('active');
-            el.classList.add('active');
-            applyFilters();
-        });
-        return el;
-    }
-
-    function applyFilters() {
-        var results = allProducts;
-
-        if (activeColor) {
-            results = results.filter(function(p) {
-                if (!p.tags) return false;
-                return p.tags.some(function(t) { return t && t.toLowerCase().indexOf(activeColor.toLowerCase()) !== -1; });
-            });
-        }
-
-        if (searchQuery) {
-            var q = searchQuery.toLowerCase();
-            results = results.filter(function(p) {
-                return (p.title && p.title.toLowerCase().indexOf(q) !== -1) ||
-                    (p.primarySku && p.primarySku.toLowerCase().indexOf(q) !== -1) ||
-                    (p.primaryColor && p.primaryColor.toLowerCase().indexOf(q) !== -1) ||
-                    (p.vendor && p.vendor.toLowerCase().indexOf(q) !== -1) ||
-                    (p.tags && p.tags.some(function(t) { return t.toLowerCase().indexOf(q) !== -1; }));
-            });
-        }
-
-        filtered = sortProducts(results);
-        displayed = 0;
-        grid.innerHTML = '';
-
-        if (filtered.length === 0) {
-            grid.innerHTML = '<div class="empty-state"><h3>No wallpapers found</h3><p>Try a different color or search term.</p><button onclick="clearFilters()">Clear Filters</button></div>';
-        } else {
-            renderBatch();
-        }
-
-        updateCounts();
-    }
-
-    function renderBatch() {
-        var end = Math.min(displayed + BATCH, filtered.length);
-        var fragment = document.createDocumentFragment();
-
-        for (var i = displayed; i < end; i++) {
-            fragment.appendChild(createCard(filtered[i], i - displayed));
-        }
-
-        grid.appendChild(fragment);
-        displayed = end;
-        scrollLoader.style.display = (displayed < filtered.length) ? 'block' : 'none';
-    }
-
-    function createCard(p, index) {
-        var card = document.createElement('article');
-        card.className = 'product-card';
-        card.style.animationDelay = (index * 30) + 'ms';
-        card.setAttribute('role', 'link');
-        card.setAttribute('tabindex', '0');
-        card.setAttribute('aria-label', p.title);
-
-        var hex = productHex(p);
-        var samplePrice = p.price ? '$' + parseFloat(p.price).toFixed(2) : '';
-        var safeAlt = (p.imageAlt || p.title || '').replace(/"/g, '&quot;');
-
-        card.innerHTML =
-            '<div class="card-img-wrap loading">' +
-                '<img data-src="' + (p.image || '') + '" alt="' + safeAlt + '" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==">' +
-                '<span class="card-color-dot" style="background:' + hex + '"></span>' +
-                (p.vendor ? '<span class="card-vendor">' + escHTML(p.vendor) + '</span>' : '') +
-            '</div>' +
-            '<div class="card-body">' +
-                '<div class="card-sku">' + escHTML(p.primarySku || '') + '</div>' +
-                '<div class="card-title">' + escHTML(p.title) + '</div>' +
-                '<div class="card-meta">' +
-                    (samplePrice ? '<span class="card-price">Sample ' + samplePrice + '</span>' : '<span></span>') +
-                    '<span class="card-cta">View Details &#8594;</span>' +
-                '</div>' +
-            '</div>';
-
-        // Lazy load image with IntersectionObserver
-        var img = card.querySelector('img');
-        var wrap = card.querySelector('.card-img-wrap');
-        lazyImages.push({ img: img, wrap: wrap });
-
-        card.addEventListener('click', function() {
-            if (p.productUrl) window.open(p.productUrl, '_blank', 'noopener');
-        });
-        card.addEventListener('keydown', function(e) {
-            if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); if (p.productUrl) window.open(p.productUrl, '_blank', 'noopener'); }
-        });
-
-        return card;
-    }
-
-    function escHTML(s) {
-        var d = document.createElement('div');
-        d.textContent = s || '';
-        return d.innerHTML;
-    }
-
-    function updateCounts() {
-        resultsText.innerHTML = '<strong>' + filtered.length + '</strong> ' +
-            (activeColor ? escHTML(activeColor) + ' ' : '') + 'wallpaper' + (filtered.length !== 1 ? 's' : '') +
-            (searchQuery ? ' matching &ldquo;' + escHTML(searchQuery) + '&rdquo;' : '');
-
-        headerCount.innerHTML = '<strong>' + filtered.length + '</strong> / ' + allProducts.length;
-    }
-
-    // Lazy image loading
-    var lazyImages = [];
-    var imgObserver = new IntersectionObserver(function(entries) {
-        entries.forEach(function(entry) {
-            if (entry.isIntersecting) {
-                var img = entry.target;
-                var src = img.getAttribute('data-src');
-                if (src) {
-                    img.src = src;
-                    img.removeAttribute('data-src');
-                    img.addEventListener('load', function() {
-                        img.parentElement.classList.remove('loading');
-                    });
-                    img.addEventListener('error', function() {
-                        img.parentElement.classList.remove('loading');
-                        var sku = img.alt || 'Image';
-                        img.src = "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='300' height='400' viewBox='0 0 300 400'%3E%3Crect fill='%23f3edf3' width='300' height='400'/%3E%3Ctext x='50%25' y='48%25' text-anchor='middle' fill='%239E929F' font-family='sans-serif' font-size='14'%3EFlocked Velvet%3C/text%3E%3C/svg%3E";
-                    });
-                }
-                imgObserver.unobserve(img);
-            }
-        });
-    }, { rootMargin: '200px' });
-
-    // Observe images after render
-    var origRenderBatch = renderBatch;
-    renderBatch = function() {
-        var prevLen = lazyImages.length;
-        origRenderBatch();
-        for (var i = prevLen; i < lazyImages.length; i++) {
-            imgObserver.observe(lazyImages[i].img);
-        }
-    };
-
-    // Search (debounced)
-    var searchTimer;
-    searchInput.addEventListener('input', function() {
-        clearTimeout(searchTimer);
-        searchTimer = setTimeout(function() {
-            searchQuery = searchInput.value.trim();
-            applyFilters();
-        }, 250);
-    });
-
-    // Sort toggle
-    var sortModes = ['color', 'name', 'newest'];
-    var sortLabels = { color: 'Sort: Color', name: 'Sort: A-Z', newest: 'Sort: Newest' };
-    sortToggle.addEventListener('click', function() {
-        var idx = (sortModes.indexOf(sortMode) + 1) % sortModes.length;
-        sortMode = sortModes[idx];
-        sortToggle.textContent = sortLabels[sortMode];
-        applyFilters();
-    });
-
-    // Infinite scroll
-    var scrollObserver = new IntersectionObserver(function(entries) {
-        if (entries[0].isIntersecting && displayed < filtered.length) {
-            renderBatch();
-        }
-    }, { rootMargin: '400px' });
-    scrollObserver.observe(scrollLoader);
-
-    // Header shadow + back-to-top
-    var ticking = false;
-    window.addEventListener('scroll', function() {
-        if (!ticking) {
-            requestAnimationFrame(function() {
-                var y = window.scrollY;
-                siteHeader.classList.toggle('scrolled', y > 10);
-                backToTop.classList.toggle('visible', y > 600);
-                ticking = false;
-            });
-            ticking = true;
-        }
-    });
-
-    backToTop.addEventListener('click', function() {
-        window.scrollTo({ top: 0, behavior: 'smooth' });
-    });
-
-    window.clearFilters = function() {
-        activeColor = null;
-        searchQuery = '';
-        searchInput.value = '';
-        var pills = document.querySelectorAll('.color-pill');
-        for (var i = 0; i < pills.length; i++) pills[i].classList.remove('active');
-        if (pills[0]) pills[0].classList.add('active');
-        applyFilters();
-    };
-
-    init();
-})();
-</script>
-
-</body>
-</html>

← 459b4e6 seo: add canonical + Open Graph + Twitter card + meta descri  ·  back to Flockedwallpaper  ·  leak fix: 404-guard raw catalog json (flocked-products.json 78c5957 →