[object Object]

← back to Designer Wallcoverings

Localize Carl Robinson viewer (:64761): local images + real-textiles filter

bd1c4ceea5a2d1744cbaea85a0b15ea4fcdcccc7 · 2026-07-06 12:52:42 -0700 · Steve

Files touched

Diff

commit bd1c4ceea5a2d1744cbaea85a0b15ea4fcdcccc7
Author: Steve <steve@designerwallcoverings.com>
Date:   Mon Jul 6 12:52:42 2026 -0700

    Localize Carl Robinson viewer (:64761): local images + real-textiles filter
---
 scripts/wallquest-refresh/localize-cr-viewer.cjs | 80 ++++++++++++++++++++++++
 1 file changed, 80 insertions(+)

diff --git a/scripts/wallquest-refresh/localize-cr-viewer.cjs b/scripts/wallquest-refresh/localize-cr-viewer.cjs
new file mode 100644
index 00000000..d3242a07
--- /dev/null
+++ b/scripts/wallquest-refresh/localize-cr-viewer.cjs
@@ -0,0 +1,80 @@
+// Localize the Carl Robinson viewer (served at :64761 from /tmp/carl-robinson-viewer) so it
+// renders in a browser: download primary images locally (no Referer → bypasses WallQuest hotlink
+// 403) + filter to REAL TEXTILES (drop cork/mica/nonwoven/wood-veneer). Preserves the sibling's
+// beach-city naming scheme. Regenerates index.html in place; adds img/ files. (Steve-requested.)
+const fs = require('fs');
+const https = require('https');
+const OUTDIR = '/tmp/carl-robinson-viewer';
+const ENR = '/tmp/wq-carl-robinson-enriched.json';
+const NORM = '/tmp/wq-carl-robinson-normalized.json';
+let recs = JSON.parse(fs.readFileSync(fs.existsSync(ENR) ? ENR : NORM, 'utf8'));
+
+// sibling's beach-city map (kept identical)
+const CITY = { Grasscloth:'Nantucket', Paperweave:'Montauk', Raffia:'Newport', Netting:'Southampton', Bamboo:'Cape May', Sisal:'Sag Harbor', Cork:'Kennebunkport', Fiber:'Rockport', Jute:'Chatham', Mica:'Bar Harbor', Nonwoven:'Provincetown', Silk:'Watch Hill', 'Wood Veneer':'Edgartown' };
+const materialOf = p => (p || '').replace(/^white naturals\s*/i, '').trim() || 'Naturals';
+
+// REAL TEXTILES ONLY — woven natural fibers; drop non-textile substrates.
+const NON_TEXTILE = /cork|mica|mother.?of.?pearl|wood.?veneer|nonwoven|non.?woven|\bshell\b|glass.?bead|vinyl/i;
+const isTextile = r => { const m = materialOf(r.pattern) + ' ' + (r.material || '') + ' ' + (r.ai_material_texture || ''); return !NON_TEXTILE.test(m); };
+const dropped = recs.filter(r => !isTextile(r));
+recs = recs.filter(isTextile);
+console.log(`real-textiles filter: kept ${recs.length}, dropped ${dropped.length} (${[...new Set(dropped.map(d=>materialOf(d.pattern)))].join(', ')})`);
+
+function dl(url, dest) { return new Promise((res) => { const rq=(u,n=0)=>{ if(n>5)return res(false); https.get(u,{timeout:15000,headers:{'User-Agent':'curl/8'}},r=>{ if(r.statusCode>=300&&r.statusCode<400&&r.headers.location)return rq(r.headers.location,n+1); if(r.statusCode!==200){r.resume();return res(false);} const ws=fs.createWriteStream(dest); r.pipe(ws); ws.on('finish',()=>{ws.close();res(true);}); }).on('error',()=>res(false)).on('timeout',function(){this.destroy();res(false);}); }; rq(url); }); }
+
+(async () => {
+  fs.mkdirSync(`${OUTDIR}/img`, { recursive: true });
+  const data = []; let got = 0;
+  for (const r of recs) {
+    const material = materialOf(r.pattern); const city = CITY[material] || 'Hamptons';
+    const remote = (r.images || []).find(u => /_650\./.test(u)) || (r.images || [])[0] || '';
+    let img = '';
+    if (remote) { const dest = `${OUTDIR}/img/${r.dw_sku}.jpg`; if (await dl(remote, dest)) { img = `img/${r.dw_sku}.jpg`; got++; } else img = remote; }
+    data.push({ dw_sku:r.dw_sku, city, material, color:r.color||'', pattern:`${city} ${material}`,
+      cost:r.price_retail, retail:r.our_price, hex:r.color_hex||'', desc:r.ai_description||'', img });
+  }
+  console.log(`downloaded ${got}/${recs.length} images locally`);
+  data.sort((a,b)=>a.city.localeCompare(b.city)||a.material.localeCompare(b.material)||a.color.localeCompare(b.color));
+  const enrichedN = data.filter(d=>d.desc).length;
+  const html = `<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
+<title>Carl Robinson E20 → Phillipe Romano — Textiles</title>
+<style>
+:root{--cols:4;--bg:#faf8f4;--ink:#1a1a1a;--mut:#8a8377;--line:#e7e2d8;--accent:#3d5a4c}
+*{box-sizing:border-box}body{margin:0;font:15px/1.5 -apple-system,BlinkMacSystemFont,"Segoe UI",Helvetica,Arial,sans-serif;background:var(--bg);color:var(--ink)}
+header{padding:22px 28px;border-bottom:1px solid var(--line);background:#fff;position:sticky;top:0;z-index:5}
+h1{margin:0 0 3px;font-size:19px;letter-spacing:.14em;text-transform:uppercase;font-weight:600}
+.sub{color:var(--mut);font-size:12.5px}
+.controls{display:flex;gap:18px;align-items:center;flex-wrap:wrap;margin-top:14px}
+.controls label{font-size:11px;text-transform:uppercase;letter-spacing:.08em;color:var(--mut);margin-right:6px}
+select{padding:5px 8px;border:1px solid var(--line);border-radius:6px;background:#fff;font-size:13px}
+.pl{margin-left:auto;font-size:11px;color:var(--mut);border:1px solid var(--line);padding:4px 9px;border-radius:20px}
+.grid{display:grid;grid-template-columns:repeat(var(--cols),1fr);gap:16px;padding:22px 28px}
+.card{background:#fff;border:1px solid var(--line);border-radius:10px;overflow:hidden;display:flex;flex-direction:column}
+.ph{aspect-ratio:1/1;background:#efeae0 center/cover no-repeat;position:relative}
+.mat{position:absolute;top:8px;left:8px;background:rgba(255,255,255,.92);font-size:10px;letter-spacing:.06em;text-transform:uppercase;padding:3px 8px;border-radius:20px;color:var(--accent);font-weight:600}
+.body{padding:11px 13px 13px}.t{font-size:13.5px;font-weight:600;line-height:1.3}.c{font-size:12px;color:var(--mut);margin-top:2px}
+.meta{display:flex;justify-content:space-between;align-items:center;margin-top:9px;font-size:11.5px;color:var(--mut)}
+.sku{font-family:ui-monospace,Menlo,monospace;font-size:11px}.px{color:var(--accent);font-weight:600}
+.dot{display:inline-block;width:10px;height:10px;border-radius:50%;border:1px solid rgba(0,0,0,.15);vertical-align:-1px;margin-right:5px}
+.desc{font-size:11.5px;color:#5c574d;margin-top:8px;display:none}body.showdesc .desc{display:block}
+</style></head><body>
+<header><h1>Carl Robinson Edition 20 · Textiles</h1>
+<div class="sub">Real woven textiles only → <b>Phillipe Romano</b> · ${data.length} products (cork/mica/nonwoven/veneer excluded) · local images</div>
+<div class="controls">
+<div><label>Sort</label><select id="sort" onchange="render()"><option value="city">City → Material → Color</option><option value="material">Material</option><option value="color">Color A→Z</option><option value="sku">SKU A→Z</option><option value="pricelow">Price ↑</option><option value="pricehigh">Price ↓</option></select></div>
+<div><label>Density</label><input type="range" min="2" max="7" value="4" oninput="document.documentElement.style.setProperty('--cols',this.value);localStorage.crCols=this.value"></div>
+<div><label><input type="checkbox" onchange="document.body.classList.toggle('showdesc',this.checked)"> descriptions</label></div>
+<span class="pl">local images · enriched ${enrichedN}/${data.length}</span></div></header>
+<div class="grid" id="grid"></div>
+<script>
+const DATA=${JSON.stringify(data)};
+function render(){const s=document.getElementById('sort').value;const d=[...DATA];
+ const by={city:(a,b)=>a.city.localeCompare(b.city)||a.material.localeCompare(b.material)||a.color.localeCompare(b.color),material:(a,b)=>a.material.localeCompare(b.material)||a.color.localeCompare(b.color),color:(a,b)=>a.color.localeCompare(b.color),sku:(a,b)=>a.dw_sku.localeCompare(b.dw_sku),pricelow:(a,b)=>a.retail-b.retail,pricehigh:(a,b)=>b.retail-a.retail};
+ d.sort(by[s]);
+ document.getElementById('grid').innerHTML=d.map(x=>\`<div class="card"><div class="ph" style="background-image:url('\${x.img}')"><span class="mat">\${x.material}</span></div><div class="body"><div class="t">\${x.pattern}</div><div class="c">\${x.hex?\`<span class="dot" style="background:\${x.hex}"></span>\`:''}\${x.color||'—'}</div><div class="meta"><span class="sku">\${x.dw_sku}</span><span><span class="px">$\${(x.retail||0).toFixed(2)}</span> · cost $\${(x.cost||0).toFixed(0)}</span></div>\${x.desc?\`<div class="desc">\${x.desc}</div>\`:''}</div></div>\`).join('');}
+if(localStorage.crCols)document.documentElement.style.setProperty('--cols',localStorage.crCols);
+render();
+</script></body></html>`;
+  fs.writeFileSync(`${OUTDIR}/index.html`, html);
+  console.log(`CR viewer localized → ${OUTDIR}/index.html (${data.length} textiles, ${got} local imgs)`);
+})();

← 7efced75 wallquest login CRACKED: real Chrome (channel:chrome persist  ·  back to Designer Wallcoverings  ·  Daisy Bennett Vol 2 live-store fixes: strip related-carousel 3b2621b4 →