← back to Contractwallpaper
graphic-loop pass 12: niche keyword filter (no off-niche products like 'solid' on patterned sites)
6491a09336400541023454cbf6ffbcbec516d361 · 2026-05-07 13:19:34 -0700 · Steve
Files touched
M server.jsA server.js.pre-pass12.bak
Diff
commit 6491a09336400541023454cbf6ffbcbec516d361
Author: Steve <steve@designerwallcoverings.com>
Date: Thu May 7 13:19:34 2026 -0700
graphic-loop pass 12: niche keyword filter (no off-niche products like 'solid' on patterned sites)
---
server.js | 26 +++++++--
server.js.pre-pass12.bak | 143 +++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 164 insertions(+), 5 deletions(-)
diff --git a/server.js b/server.js
index 1ee6f5a..8c65f63 100644
--- a/server.js
+++ b/server.js
@@ -36,6 +36,22 @@ const PRODUCTS = DATA_RAW.filter(p => !isJunk(p));
const DROPPED = DATA_RAW.length - PRODUCTS.length;
console.log(`Loaded ${DATA_RAW.length}, kept ${PRODUCTS.length}, dropped ${DROPPED}`);
+// graphics-loop pass 12: niche keyword filter — only show products that fit this site's niche
+const NICHE_POS = ["contract","class a","commercial","hospitality","healthcare","type ii"];
+const NICHE_NEG = ["solid","blank","plain"];
+function nicheFit(p) {
+ const blob = ((p.title || '') + ' ' + (p.tags || []).join(' ')).toLowerCase();
+ if (NICHE_NEG.some(n => blob.includes(n.toLowerCase()))) return false;
+ return NICHE_POS.length === 0 || NICHE_POS.some(k => blob.includes(k.toLowerCase()));
+}
+const PRODUCTS_NICHE = PRODUCTS.filter(nicheFit);
+const NICHE_DROPPED = PRODUCTS.length - PRODUCTS_NICHE.length;
+console.log(`Niche filter: kept ${PRODUCTS_NICHE.length} of ${PRODUCTS.length}, dropped ${NICHE_DROPPED}`);
+// Replace PRODUCTS reference with the niche-filtered list for all downstream handlers.
+const PRODUCTS_ORIG = PRODUCTS;
+Object.defineProperty(global, '__products_niche_applied', { value: true });
+
+
const app = express();
// Security headers via helmet (added 2026-05-04 overnight YOLO loop)
// DW STANDARD: sort by color/style/sku/title for any product-rendering site.
@@ -79,7 +95,7 @@ app.use(express.static(path.join(__dirname, 'public')));
app.get('/api/products', (req, res) => {
const { q, aesthetic, vendor, page = 1, limit = 24, sort = 'newest'} = req.query;
- let list = PRODUCTS;
+ let list = PRODUCTS_NICHE;
if (q) {
const needle = q.toLowerCase();
list = list.filter(p => (p.title || '').toLowerCase().includes(needle) || (p.tags || []).some(t => t.toLowerCase().includes(needle)));
@@ -99,7 +115,7 @@ app.get('/api/sliders', (req, res) => {
const SLIDER_AESTHETICS = ["hospitality","healthcare","commercial","natural","abstract","botanical"];
const out = [];
for (const a of SLIDER_AESTHETICS) {
- const items = PRODUCTS.filter(p => p.aesthetic === a).slice(0, 12);
+ const items = PRODUCTS_NICHE.filter(p => p.aesthetic === a).slice(0, 12);
if (items.length >= 4) out.push({ aesthetic: a, items });
}
res.json({ rails: out });
@@ -107,17 +123,17 @@ app.get('/api/sliders', (req, res) => {
app.get('/api/facets', (req, res) => {
const aesthetics = {}; const vendors = {};
- for (const p of PRODUCTS) {
+ for (const p of PRODUCTS_NICHE) {
aesthetics[p.aesthetic] = (aesthetics[p.aesthetic] || 0) + 1;
vendors[p.vendor] = (vendors[p.vendor] || 0) + 1;
}
res.json({ aesthetics, vendors, total: PRODUCTS.length });
});
-app.get('/api/health', (req, res) => res.json({ status: 'ok', count: PRODUCTS.length, dropped: DROPPED }));
+app.get('/api/health', (req, res) => res.json({ status: 'ok', count: PRODUCTS_NICHE.length, dropped: DROPPED }));
app.get('/sample/:handle', (req, res) => {
- const p = PRODUCTS.find(x => x.handle === req.params.handle || x.sku === req.params.handle);
+ const p = PRODUCTS_NICHE.find(x => x.handle === req.params.handle || x.sku === req.params.handle);
if (!p) return res.status(404).send('Not found');
res.redirect(302, p.product_url || `${DW_SHOPIFY}/products/${encodeURIComponent(p.handle)}#sample`);
});
diff --git a/server.js.pre-pass12.bak b/server.js.pre-pass12.bak
new file mode 100644
index 0000000..1ee6f5a
--- /dev/null
+++ b/server.js.pre-pass12.bak
@@ -0,0 +1,143 @@
+/**
+ * CONTRACT WALLPAPER — DW family vertical
+ * Curated slice from live designerwallcoverings.com Shopify catalog.
+ */
+const express = require('express');
+const helmet = require('helmet');
+const path = require('path');
+const fs = require('fs');
+
+const PORT = process.env.PORT || 9849;
+const DW_SHOPIFY = 'https://designerwallcoverings.com';
+const __SITE = path.basename(__dirname);
+let DATA_RAW;
+try {
+ const raw = fs.readFileSync(path.join(__dirname, 'data', 'products.json'), 'utf8');
+ DATA_RAW = JSON.parse(raw);
+ if (!Array.isArray(DATA_RAW)) throw new Error('products.json must be an array');
+} catch (e) {
+ console.error(`[${__SITE}] FATAL: could not load products.json — ${e.message}`);
+ console.error(`[${__SITE}] Starting with empty catalog. Run pull script to populate data/products.json.`);
+ DATA_RAW = [];
+}
+
+function isJunk(p) {
+ if (!p.image_url || !p.image_url.trim()) return true;
+ if (!p.handle && !p.sku) return true;
+ const t = p.title || '';
+ if (/lamp|rug|pillow|throw|tripod|frame|mirror|vase|candle|sculpture|figurine/i.test(t)) return true;
+ if (/visual.{0,3}merchandiser/i.test(t)) return true;
+ if (/(?:^|\W)image[ _-]?4(?:\W|$)/i.test(t)) return true;
+ if (/bh.?90210|beverly.?hills.?90210|iconic.{0,4}bh/i.test(t)) return true;
+ return false;
+}
+
+const PRODUCTS = DATA_RAW.filter(p => !isJunk(p));
+const DROPPED = DATA_RAW.length - PRODUCTS.length;
+console.log(`Loaded ${DATA_RAW.length}, kept ${PRODUCTS.length}, dropped ${DROPPED}`);
+
+const app = express();
+// Security headers via helmet (added 2026-05-04 overnight YOLO loop)
+// DW STANDARD: sort by color/style/sku/title for any product-rendering site.
+const COLOR_RANK = {
+ black:0,gray:1,grey:1,silver:2,white:3,ivory:4,cream:5,beige:6,brown:7,tan:8,khaki:9,copper:10,
+ red:20,pink:21,coral:22,orange:23,peach:24,salmon:25,
+ yellow:30,gold:31,mustard:32,olive:33,
+ green:40,lime:41,mint:42,teal:50,aqua:51,turquoise:52,cyan:53,
+ blue:60,navy:61,indigo:62,periwinkle:63,
+ purple:70,violet:71,lavender:72,plum:73,magenta:74,fuchsia:75,
+};
+function colorRank(p) {
+ const tags = (p.tags || []).map(t => String(t).toLowerCase());
+ for (const t of tags) for (const k of Object.keys(COLOR_RANK)) if (t.includes(k)) return COLOR_RANK[k];
+ return 999;
+}
+const STYLE_TAGS = ['traditional','transitional','modern','contemporary','minimalist','art deco','victorian','mid-century','mid century','rustic','industrial','farmhouse','bohemian','boho','geometric','floral','botanical','damask','toile','grasscloth','silk','linen','vinyl','natural'];
+function styleKey(p) {
+ const tags = (p.tags || []).map(t => String(t).toLowerCase());
+ for (const s of STYLE_TAGS) if (tags.some(t => t.includes(s))) return s;
+ return 'zzz';
+}
+function sortProducts(list, mode) {
+ if (mode === 'sku') return [...list].sort((a,b) => String(a.sku || a.handle || '').localeCompare(String(b.sku || b.handle || '')));
+ if (mode === 'title') return [...list].sort((a,b) => String(a.title || '').localeCompare(String(b.title || '')));
+ if (mode === 'color') return [...list].sort((a,b) => colorRank(a) - colorRank(b) || String(a.title || '').localeCompare(String(b.title || '')));
+ if (mode === 'style') return [...list].sort((a,b) => styleKey(a).localeCompare(styleKey(b)) || String(a.title || '').localeCompare(String(b.title || '')));
+ if (mode === 'price-asc') return [...list].sort((a,b) => (Number(a.max_price) || 0) - (Number(b.max_price) || 0));
+ if (mode === 'price-desc') return [...list].sort((a,b) => (Number(b.max_price) || 0) - (Number(a.max_price) || 0));
+ return list;
+}
+
+
+app.use(helmet({ contentSecurityPolicy: false }));
+app.use(express.json({ limit: '256kb' }));
+// Universal contact module — modals, /api/send-inquiry, /api/send-sample, /zd-loader.js
+require('./_universal-contact')(app, { siteName: "Contract Wallpaper", zdColor: "#7ea8d4", zdPosition: 'right' });
+require('./_universal-auth')(app, { siteName: "contractwallpaper" });
+
+app.use(express.static(path.join(__dirname, 'public')));
+
+app.get('/api/products', (req, res) => {
+ const { q, aesthetic, vendor, page = 1, limit = 24, sort = 'newest'} = req.query;
+ let list = PRODUCTS;
+ if (q) {
+ const needle = q.toLowerCase();
+ list = list.filter(p => (p.title || '').toLowerCase().includes(needle) || (p.tags || []).some(t => t.toLowerCase().includes(needle)));
+ }
+ if (aesthetic && aesthetic !== 'all') list = list.filter(p => p.aesthetic === aesthetic);
+ if (vendor && vendor !== 'all') list = list.filter(p => p.vendor === vendor);
+ list = sortProducts(list, sort);
+ const total = list.length;
+ const pageNum = Math.max(1, parseInt(page) || 1);
+ const lim = Math.min(60, parseInt(limit) || 24);
+ const start = (pageNum - 1) * lim;
+ res.json({ total, page: pageNum, limit: lim, pages: Math.ceil(total/lim), sort, items: list.slice(start, start + lim) });
+ // (replaced) old: pages: Math.ceil(total / lim), items: list.slice(start, start + lim) });
+});
+
+app.get('/api/sliders', (req, res) => {
+ const SLIDER_AESTHETICS = ["hospitality","healthcare","commercial","natural","abstract","botanical"];
+ const out = [];
+ for (const a of SLIDER_AESTHETICS) {
+ const items = PRODUCTS.filter(p => p.aesthetic === a).slice(0, 12);
+ if (items.length >= 4) out.push({ aesthetic: a, items });
+ }
+ res.json({ rails: out });
+});
+
+app.get('/api/facets', (req, res) => {
+ const aesthetics = {}; const vendors = {};
+ for (const p of PRODUCTS) {
+ aesthetics[p.aesthetic] = (aesthetics[p.aesthetic] || 0) + 1;
+ vendors[p.vendor] = (vendors[p.vendor] || 0) + 1;
+ }
+ res.json({ aesthetics, vendors, total: PRODUCTS.length });
+});
+
+app.get('/api/health', (req, res) => res.json({ status: 'ok', count: PRODUCTS.length, dropped: DROPPED }));
+
+app.get('/sample/:handle', (req, res) => {
+ const p = PRODUCTS.find(x => x.handle === req.params.handle || x.sku === req.params.handle);
+ if (!p) return res.status(404).send('Not found');
+ res.redirect(302, p.product_url || `${DW_SHOPIFY}/products/${encodeURIComponent(p.handle)}#sample`);
+});
+
+// sitemap.xml + robots.txt for SEO
+app.get('/robots.txt', (req, res) => {
+ res.type('text/plain').send(`User-agent: *
+Allow: /
+Sitemap: https://contractwallpaper.com/sitemap.xml
+`);
+});
+app.get('/sitemap.xml', (req, res) => {
+ const urls = ['/'];
+ const xml = `<?xml version="1.0" encoding="UTF-8"?>
+<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
+${urls.map(u => ` <url><loc>https://contractwallpaper.com${u}</loc><changefreq>weekly</changefreq></url>`).join('\n')}
+</urlset>`;
+ res.type('application/xml').send(xml);
+});
+
+app.listen(PORT, '127.0.0.1', () => {
+ console.log(`contractwallpaper listening on http://127.0.0.1:${PORT}`);
+});
← 36f2610 graphic-loop pass 11: add Ideas rails (New this week + Curat
·
back to Contractwallpaper
·
graphic-loop pass 13: real footer brand + Facebook link + OG 26256f1 →