← back to Dw Yolo Loop
Artmura site (B): 'Pairs well with' recs + per-product JSON-LD on PDP
528cdf42a25f07b7987018b1cc3c5fc3495233b0 · 2026-06-12 09:27:11 -0700 · Steve Abrams
- /api/pairs/:handle — interior-designer logic (same/adjacent color family, different motif series, same book bonus); 6 picks
- PDP pairs row (6 swatches) + schema.org Product JSON-LD (sku, brand, image, offer)
Files touched
M artmura-site/public/product.htmlM artmura-site/public/styles.cssM artmura-site/server.js
Diff
commit 528cdf42a25f07b7987018b1cc3c5fc3495233b0
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Fri Jun 12 09:27:11 2026 -0700
Artmura site (B): 'Pairs well with' recs + per-product JSON-LD on PDP
- /api/pairs/:handle — interior-designer logic (same/adjacent color family, different motif series, same book bonus); 6 picks
- PDP pairs row (6 swatches) + schema.org Product JSON-LD (sku, brand, image, offer)
---
artmura-site/public/product.html | 26 +++++++++++++++++++++++++-
artmura-site/public/styles.css | 11 +++++++++++
artmura-site/server.js | 18 ++++++++++++++++++
3 files changed, 54 insertions(+), 1 deletion(-)
diff --git a/artmura-site/public/product.html b/artmura-site/public/product.html
index dc2ba3e..16a972f 100644
--- a/artmura-site/public/product.html
+++ b/artmura-site/public/product.html
@@ -65,7 +65,31 @@ function specRow(k,v){return v?`<div class="row"><span class="k">${k}</span><spa
<div><h3>The story</h3><div>${p.body_html||'<p>'+esc(p.title)+' by Artmura.</p>'}</div></div>
<div><h3>Specifications</h3><p>${esc(p.width||'')} · ${esc(p.substrate||'Non-Woven')} · ${esc(p.grade||'Residential')} grade · ${esc(p.coverage||'')} · ${esc(p.origin||'Made in Italy')}.</p></div>
<div><h3>Care & install</h3><p>Paste-the-wall non-woven · spongeable · lead-time ${esc(p.lead_time||'3–4 weeks')}. Order a sample before specifying.</p></div>
- </div>`;
+ </div>
+ <section class="pairs" id="pairs" hidden>
+ <h3>Pairs well with</h3>
+ <div class="pair-grid" id="pairGrid"></div>
+ </section>`;
+
+ // JSON-LD Product schema
+ const ld={ "@context":"https://schema.org","@type":"Product",name:p.title+" by Artmura",sku:p.sku,
+ brand:{"@type":"Brand",name:"Artmura"},category:"Wallcoverings",image:imgs,
+ description:(p.body_html||'').replace(/<[^>]+>/g,'').slice(0,300),
+ offers:{"@type":"Offer",priceCurrency:"USD",price:String(p.price||''),availability:"https://schema.org/InStock"}};
+ const s=document.createElement('script');s.type='application/ld+json';s.textContent=JSON.stringify(ld);document.head.appendChild(s);
+
+ // pairs well with
+ try{
+ const pr=await (await fetch('/api/pairs/'+handle)).json();
+ if(pr.pairs&&pr.pairs.length){
+ document.getElementById('pairGrid').innerHTML=pr.pairs.map(q=>`
+ <a class="pcard" href="/product/${q.handle}">
+ <img loading="lazy" decoding="async" alt="${esc(q.title)}" src="${q.swatch||q.room||''}">
+ <div class="pc-b"><span class="pc-s">${esc(q.series||'')}</span><span class="pc-n">${esc(q.color||q.title)}</span></div>
+ </a>`).join('');
+ document.getElementById('pairs').hidden=false;
+ }
+ }catch(e){}
// gallery swap
document.querySelectorAll('.thumbs .t').forEach(t=>t.onclick=()=>{
diff --git a/artmura-site/public/styles.css b/artmura-site/public/styles.css
index f27a152..7083110 100644
--- a/artmura-site/public/styles.css
+++ b/artmura-site/public/styles.css
@@ -153,6 +153,17 @@ footer{border-top:1px solid var(--line);background:var(--paper)}
padding-top:40px;border-top:1px solid var(--line)}
.story h3{font-family:var(--serif);font-weight:500;font-size:22px;margin:0 0 12px}
.story p{font-size:13.5px;color:var(--ink-soft);margin:0}
+.pairs{margin-top:60px;padding-top:40px;border-top:1px solid var(--line)}
+.pairs h3{font-family:var(--serif);font-weight:500;font-size:24px;margin:0 0 22px}
+.pair-grid{display:grid;grid-template-columns:repeat(6,1fr);gap:18px}
+.pcard{display:block}
+.pcard img{aspect-ratio:1/1;width:100%;object-fit:cover;border:1px solid var(--line);border-radius:3px;transition:.25s}
+.pcard:hover img{transform:translateY(-3px);box-shadow:0 14px 32px -20px rgba(33,29,24,.5)}
+.pc-b{padding:9px 2px}
+.pc-s{display:block;font-size:9px;letter-spacing:.16em;text-transform:uppercase;color:var(--gold)}
+.pc-n{display:block;font-family:var(--serif);font-size:16px;line-height:1.1;margin-top:2px}
+@media(max-width:1080px){.pair-grid{grid-template-columns:repeat(3,1fr)}}
+@media(max-width:560px){.pair-grid{grid-template-columns:repeat(2,1fr)}}
.preview-flag{position:fixed;bottom:0;left:0;right:0;z-index:50;background:#211d18;color:#f6f2ec;
font-size:11px;letter-spacing:.05em;text-align:center;padding:7px 14px}
.preview-flag b{color:#caa45f}
diff --git a/artmura-site/server.js b/artmura-site/server.js
index 9b34a70..64b349a 100644
--- a/artmura-site/server.js
+++ b/artmura-site/server.js
@@ -67,6 +67,24 @@ app.get('/api/product/:handle', (req, res) => {
if (!p) return res.status(404).json({ error: 'not found' });
res.json(p);
});
+// "Pairs well with" — interior-designer logic: same/adjacent color family, different
+// pattern series (contrast of scale), prefer same collection book. Returns up to 6.
+app.get('/api/pairs/:handle', (req, res) => {
+ const p = CATALOG.find(x => x.handle === req.params.handle);
+ if (!p) return res.status(404).json({ error: 'not found' });
+ const hueDist = (a, b) => { if (a == null || b == null) return 180; const d = Math.abs(a - b) % 360; return d > 180 ? 360 - d : d; };
+ const scored = CATALOG.filter(x => x.handle !== p.handle).map(x => {
+ let s = 0;
+ if (x.color_bucket && x.color_bucket === p.color_bucket) s += 40; // same color family
+ s += Math.max(0, 30 - hueDist(x.hue, p.hue) / 2); // hue proximity
+ if (x.series !== p.series) s += 20; // different motif/scale
+ if (x.book && x.book === p.book) s += 12; // same book
+ if (x.color_bucket && ['grey', 'white', 'black'].includes(x.color_bucket) && !['grey','white','black'].includes(p.color_bucket)) s += 8; // neutral pairs with chromatic
+ return { x, s };
+ }).sort((a, b) => b.s - a.s).slice(0, 6).map(o => o.x);
+ res.json({ pairs: scored });
+});
+
app.get('/api/facets', (_req, res) => {
const books = {}, series = {}, colors = {};
for (const p of CATALOG) {
← 3e3a3bd Artmura site (B/catalog): dominant-color extraction → true-h
·
back to Dw Yolo Loop
·
Artmura site: README documenting features, data, endpoints, 7a52550 →