← 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
M .gitignoreD diagnostic.htmlM server.jsD simple-test.html
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 — 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>© 2025 <a href="/">FlockedWallpaper.com</a> — 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, '"');
-
- 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 →</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 “' + escHTML(searchQuery) + '”' : '');
-
- 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 →