[object Object]

← back to 1900swallpaper

graphic-loop pass 12: niche keyword filter (no off-niche products like 'solid' on patterned sites)

da28f364eeba71d04b85608cf05f687c8b1cabe7 · 2026-05-07 13:19:16 -0700 · Steve

Files touched

Diff

commit da28f364eeba71d04b85608cf05f687c8b1cabe7
Author: Steve <steve@designerwallcoverings.com>
Date:   Thu May 7 13:19:16 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 413c658..e07f2f5 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 = ["edwardian","art nouveau","nouveau","arts and crafts","william morris"];
+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 = ["nouveau","edwardian","craftsman","botanical","chinoiserie","geometric"];
   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..413c658
--- /dev/null
+++ b/server.js.pre-pass12.bak
@@ -0,0 +1,143 @@
+/**
+ * 1900s WALLPAPER — DW family vertical
+ * Curated slice from live designerwallcoverings.com Shopify catalog.
+ */
+try { require('dotenv').config({ path: require('path').join(__dirname, '.env') }); } catch (e) {}
+const express = require('express');
+const helmet = require('helmet');
+const path = require('path');
+const fs = require('fs');
+
+const PORT = process.env.PORT || 9858;
+const DW_SHOPIFY = 'https://designerwallcoverings.com';
+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('[1900swallpaper] FATAL: could not load products.json —', e.message);
+  console.error('[1900swallpaper] 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: "1900s Wallpaper", zdColor: "#a89060", zdPosition: 'right' });
+require('./_universal-auth')(app, { siteName: "1900swallpaper" });
+
+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 = ["nouveau","edwardian","craftsman","botanical","chinoiserie","geometric"];
+  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://1900swallpaper.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://1900swallpaper.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(`1900swallpaper listening on http://127.0.0.1:${PORT}`);
+});

← c181ef1 graphic-loop pass 11: add Ideas rails (New this week + Curat  ·  back to 1900swallpaper  ·  graphic-loop pass 13: real footer brand + Facebook link + OG f3cfe88 →