[object Object]

← back to Dw Yolo Loop

Artmura: apply Cost/.75/.85 retail markup + hide prices on landing + add social share

8382b47dd7fbf4529d1795872cc58326ff3080f8 · 2026-06-12 11:49:14 -0700 · Steve Abrams

- Marked up 161 live Yard variants to round(newwall_price/.75/.85) whole-dollar ($369–$565); samples stay $5. apply-markup.js keys off immutable cost basis (idempotent). 161/161 updated, 0 failed; verified live.
- Landing no longer shows any price (Steve: link to the real product for price). Cards show 'View →'; PDP CTA 'View & price on Designer Wallcoverings'; removed Price sort options; stripped price from JSON-LD.
- Added social share (Pinterest/Facebook/X/Email/Copy) — PDP shares the real DW product page, homepage shares the real DW collection.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

Files touched

Diff

commit 8382b47dd7fbf4529d1795872cc58326ff3080f8
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Fri Jun 12 11:49:14 2026 -0700

    Artmura: apply Cost/.75/.85 retail markup + hide prices on landing + add social share
    
    - Marked up 161 live Yard variants to round(newwall_price/.75/.85) whole-dollar ($369–$565); samples stay $5. apply-markup.js keys off immutable cost basis (idempotent). 161/161 updated, 0 failed; verified live.
    - Landing no longer shows any price (Steve: link to the real product for price). Cards show 'View →'; PDP CTA 'View & price on Designer Wallcoverings'; removed Price sort options; stripped price from JSON-LD.
    - Added social share (Pinterest/Facebook/X/Email/Copy) — PDP shares the real DW product page, homepage shares the real DW collection.
    
    Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---
 artmura-site/public/index.html          | 32 +++++++++++--
 artmura-site/public/product.html        | 39 ++++++++++++++--
 artmura-site/public/styles.css          | 17 ++++++-
 scripts/artmura-onboard/apply-markup.js | 83 +++++++++++++++++++++++++++++++++
 4 files changed, 162 insertions(+), 9 deletions(-)

diff --git a/artmura-site/public/index.html b/artmura-site/public/index.html
index 31a0535..7d55bb6 100644
--- a/artmura-site/public/index.html
+++ b/artmura-site/public/index.html
@@ -55,8 +55,6 @@
         <option value="series">Collection</option>
         <option value="sku">SKU A→Z</option>
         <option value="title">Title A→Z</option>
-        <option value="price_asc">Price ↑</option>
-        <option value="price_desc">Price ↓</option>
       </select>
     </div>
     <div class="ctl">
@@ -80,6 +78,7 @@
       <a class="wm" href="https://www.designerwallcoverings.com">Designer Wallcoverings</a>
       <div style="margin-top:8px">Designer Wallcoverings &amp; Fabrics · To the Trade</div>
       <div style="margin-top:4px;opacity:.8">The Artmura Collection — one of 200+ lines we carry.</div>
+      <div class="share" id="shareCollection" style="margin-top:18px"></div>
     </div>
     <div class="gate">Architectural Italian non-woven wallcoverings, sold by the yard at Designer Wallcoverings. Order a sample before specifying. <a href="https://www.designerwallcoverings.com/collections/artmura" style="color:var(--gold)">Shop the Artmura collection →</a></div>
   </div>
@@ -148,7 +147,7 @@ function render(){
         <div class="nm">${p.color||p.title}</div>
         <div class="meta">
           <span class="col">${p.hex?`<span class="dot" style="background:${p.hex}"></span>`:''}${p.book||'Artmura'}</span>
-          <span class="pr">$${(p.price||0).toFixed(0)} <small>/yd</small></span>
+          <span class="pr view">View →</span>
         </div>
       </div>
     </div>`).join('');
@@ -197,6 +196,33 @@ function applyConfig(cfg){
   if(cfg.kicker)  $('#heroKicker').textContent=cfg.kicker;
   if(cfg.title)   document.title=cfg.title;
   if(cfg.collectionUrl){ const sb=$('#shopbar'); if(sb)sb.href=cfg.collectionUrl; }
+  renderShare(cfg.collectionUrl||cfg.storeBase, (cfg.title||'Artmura at Designer Wallcoverings'));
+}
+
+// social share cluster — shares the real DW collection page
+function renderShare(url, title){
+  const el=$('#shareCollection'); if(!el||!url)return;
+  const U=encodeURIComponent(url), T=encodeURIComponent(title);
+  const ic={
+    pin:'<svg viewBox="0 0 24 24" width="18" height="18" fill="currentColor"><path d="M12 2C6.5 2 4 5.8 4 8.9c0 1.9.7 3.6 2.3 4.2.3.1.5 0 .6-.3l.2-.9c.1-.3 0-.4-.2-.6-.4-.5-.7-1.2-.7-2.1 0-2.7 2-5.1 5.3-5.1 2.9 0 4.5 1.8 4.5 4.1 0 3.1-1.4 5.7-3.4 5.7-1.1 0-1.9-.9-1.7-2 .3-1.3.9-2.7.9-3.6 0-.8-.4-1.5-1.4-1.5-1.1 0-2 1.1-2 2.7 0 1 .3 1.6.3 1.6l-1.4 5.7c-.4 1.6 0 3.7 0 3.9 0 .1.2.2.3.1.1-.1 1.6-2 2.1-3.8l.8-3c.4.8 1.5 1.4 2.7 1.4 3.6 0 6-3.3 6-7.6C20.4 5 17 2 12 2z"/></svg>',
+    fb:'<svg viewBox="0 0 24 24" width="18" height="18" fill="currentColor"><path d="M22 12a10 10 0 10-11.6 9.9v-7H7.9V12h2.5V9.8c0-2.5 1.5-3.9 3.8-3.9 1.1 0 2.2.2 2.2.2v2.5h-1.2c-1.2 0-1.6.8-1.6 1.6V12h2.7l-.4 2.9h-2.3v7A10 10 0 0022 12z"/></svg>',
+    x:'<svg viewBox="0 0 24 24" width="16" height="16" fill="currentColor"><path d="M18.9 2H22l-7.3 8.3L23.3 22h-6.8l-5.3-7-6.1 7H2l7.8-8.9L1.2 2H8l4.8 6.3L18.9 2zm-1.2 18h1.9L7.1 4H5.1l12.6 16z"/></svg>',
+    mail:'<svg viewBox="0 0 24 24" width="18" height="18" fill="currentColor"><path d="M4 4h16a2 2 0 012 2v12a2 2 0 01-2 2H4a2 2 0 01-2-2V6a2 2 0 012-2zm8 7L4 6.5V18h16V6.5L12 11z"/></svg>',
+    link:'<svg viewBox="0 0 24 24" width="17" height="17" fill="currentColor"><path d="M10.6 13.4a1 1 0 001.4 0l4-4a3 3 0 00-4.2-4.2l-1.3 1.3a1 1 0 101.4 1.4l1.3-1.3a1 1 0 011.4 1.4l-4 4a1 1 0 000 1.4zm2.8-2.8a1 1 0 00-1.4 0l-4 4a3 3 0 004.2 4.2l1.3-1.3a1 1 0 10-1.4-1.4l-1.3 1.3a1 1 0 01-1.4-1.4l4-4a1 1 0 000-1.4z"/></svg>',
+  };
+  el.innerHTML=`<span class="share-lbl">Share</span>
+    <a class="sbtn pin" title="Pin on Pinterest" target="_blank" rel="noopener" href="https://pinterest.com/pin/create/button/?url=${U}&description=${T}">${ic.pin}</a>
+    <a class="sbtn fb" title="Share on Facebook" target="_blank" rel="noopener" href="https://www.facebook.com/sharer/sharer.php?u=${U}">${ic.fb}</a>
+    <a class="sbtn x" title="Share on X" target="_blank" rel="noopener" href="https://twitter.com/intent/tweet?url=${U}&text=${T}">${ic.x}</a>
+    <a class="sbtn mail" title="Email" href="mailto:?subject=${T}&body=${T}%20${U}">${ic.mail}</a>
+    <button class="sbtn copy" title="Copy link" type="button">${ic.link}</button>`;
+  el.querySelector('.copy').addEventListener('click',async()=>{
+    const b=el.querySelector('.copy');
+    try{ if(navigator.share){await navigator.share({title,url});return;}
+      await navigator.clipboard.writeText(url); b.classList.add('done'); b.title='Copied!';
+      setTimeout(()=>{b.classList.remove('done');b.title='Copy link';},1600);
+    }catch(e){}
+  });
 }
 
 (async()=>{
diff --git a/artmura-site/public/product.html b/artmura-site/public/product.html
index 1c4867a..85f702a 100644
--- a/artmura-site/public/product.html
+++ b/artmura-site/public/product.html
@@ -47,9 +47,9 @@ function specRow(k,v){return v?`<div class="row"><span class="k">${k}</span><spa
         <div class="ser">${esc(p.series)} · ${esc(p.book||'Artmura')}</div>
         <h1>${esc(p.color||p.title)}</h1>
         <div class="colr">${esc(p.sku)} · ${esc(p.substrate||'Non-Woven')}</div>
-        <div class="price">$${(p.price||0).toFixed(0)} <small>per yard</small></div>
-        <a class="cta" href="${esc(p.store_url)}" target="_blank" rel="noopener">Shop on Designer Wallcoverings →</a>
-        <a class="cta ghost" href="${esc(p.store_url)}" target="_blank" rel="noopener">Order a sample · $${(p.sample_price||5).toFixed(0)}</a>
+        <a class="cta" href="${esc(p.store_url)}" target="_blank" rel="noopener">View &amp; price on Designer Wallcoverings →</a>
+        <a class="cta ghost" href="${esc(p.store_url)}" target="_blank" rel="noopener">Order a sample</a>
+        <div class="share" id="share" data-url="${esc(p.store_url)}" data-title="${esc((p.color||p.title)+' · Artmura | Designer Wallcoverings')}" data-img="${esc(main)}"></div>
         <div class="spec">
           ${specRow('Item code',p.sku)}
           ${specRow('Grade',p.grade)}
@@ -73,10 +73,11 @@ function specRow(k,v){return v?`<div class="row"><span class="k">${k}</span><spa
     </section>`;
 
   // JSON-LD Product schema
+  // No price in JSON-LD — pricing lives on the canonical DW product page (offers → its URL)
   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"}};
+    offers:{"@type":"Offer",availability:"https://schema.org/InStock",url:p.store_url}};
   const s=document.createElement('script');s.type='application/ld+json';s.textContent=JSON.stringify(ld);document.head.appendChild(s);
 
   // pairs well with
@@ -99,7 +100,37 @@ function specRow(k,v){return v?`<div class="row"><span class="k">${k}</span><spa
   });
   // SEO: canonical → the live Shopify product (this landing complements, not competes)
   if(p.store_url){const c=document.createElement('link');c.rel='canonical';c.href=p.store_url;document.head.appendChild(c);}
+
+  // social share — every target shares the REAL Designer Wallcoverings product page
+  renderShare();
 })();
+
+function renderShare(){
+  const el=document.getElementById('share'); if(!el)return;
+  const url=el.dataset.url, title=el.dataset.title||'', img=el.dataset.img||'';
+  const U=encodeURIComponent(url), T=encodeURIComponent(title), I=encodeURIComponent(img);
+  const ic={
+    pin:'<svg viewBox="0 0 24 24" width="18" height="18" fill="currentColor"><path d="M12 2C6.5 2 4 5.8 4 8.9c0 1.9.7 3.6 2.3 4.2.3.1.5 0 .6-.3l.2-.9c.1-.3 0-.4-.2-.6-.4-.5-.7-1.2-.7-2.1 0-2.7 2-5.1 5.3-5.1 2.9 0 4.5 1.8 4.5 4.1 0 3.1-1.4 5.7-3.4 5.7-1.1 0-1.9-.9-1.7-2 .3-1.3.9-2.7.9-3.6 0-.8-.4-1.5-1.4-1.5-1.1 0-2 1.1-2 2.7 0 1 .3 1.6.3 1.6l-1.4 5.7c-.4 1.6 0 3.7 0 3.9 0 .1.2.2.3.1.1-.1 1.6-2 2.1-3.8l.8-3c.4.8 1.5 1.4 2.7 1.4 3.6 0 6-3.3 6-7.6C20.4 5 17 2 12 2z"/></svg>',
+    fb:'<svg viewBox="0 0 24 24" width="18" height="18" fill="currentColor"><path d="M22 12a10 10 0 10-11.6 9.9v-7H7.9V12h2.5V9.8c0-2.5 1.5-3.9 3.8-3.9 1.1 0 2.2.2 2.2.2v2.5h-1.2c-1.2 0-1.6.8-1.6 1.6V12h2.7l-.4 2.9h-2.3v7A10 10 0 0022 12z"/></svg>',
+    x:'<svg viewBox="0 0 24 24" width="16" height="16" fill="currentColor"><path d="M18.9 2H22l-7.3 8.3L23.3 22h-6.8l-5.3-7-6.1 7H2l7.8-8.9L1.2 2H8l4.8 6.3L18.9 2zm-1.2 18h1.9L7.1 4H5.1l12.6 16z"/></svg>',
+    mail:'<svg viewBox="0 0 24 24" width="18" height="18" fill="currentColor"><path d="M4 4h16a2 2 0 012 2v12a2 2 0 01-2 2H4a2 2 0 01-2-2V6a2 2 0 012-2zm8 7L4 6.5V18h16V6.5L12 11z"/></svg>',
+    link:'<svg viewBox="0 0 24 24" width="17" height="17" fill="currentColor"><path d="M10.6 13.4a1 1 0 001.4 0l4-4a3 3 0 00-4.2-4.2l-1.3 1.3a1 1 0 101.4 1.4l1.3-1.3a1 1 0 011.4 1.4l-4 4a1 1 0 000 1.4zm2.8-2.8a1 1 0 00-1.4 0l-4 4a3 3 0 004.2 4.2l1.3-1.3a1 1 0 10-1.4-1.4l-1.3 1.3a1 1 0 01-1.4-1.4l4-4a1 1 0 000-1.4z"/></svg>',
+  };
+  el.innerHTML=`<span class="share-lbl">Share</span>
+    <a class="sbtn pin" title="Pin on Pinterest" target="_blank" rel="noopener" href="https://pinterest.com/pin/create/button/?url=${U}&media=${I}&description=${T}">${ic.pin}</a>
+    <a class="sbtn fb" title="Share on Facebook" target="_blank" rel="noopener" href="https://www.facebook.com/sharer/sharer.php?u=${U}">${ic.fb}</a>
+    <a class="sbtn x" title="Share on X" target="_blank" rel="noopener" href="https://twitter.com/intent/tweet?url=${U}&text=${T}">${ic.x}</a>
+    <a class="sbtn mail" title="Email" href="mailto:?subject=${T}&body=${T}%20${U}">${ic.mail}</a>
+    <button class="sbtn copy" title="Copy link" type="button">${ic.link}</button>`;
+  const copy=el.querySelector('.copy');
+  copy.addEventListener('click',async()=>{
+    try{
+      if(navigator.share){await navigator.share({title:el.dataset.title,url});return;}
+      await navigator.clipboard.writeText(url); copy.classList.add('done');
+      copy.title='Copied!'; setTimeout(()=>{copy.classList.remove('done');copy.title='Copy link';},1600);
+    }catch(e){}
+  });
+}
 </script>
 </body>
 </html>
diff --git a/artmura-site/public/styles.css b/artmura-site/public/styles.css
index 35e16ca..c790017 100644
--- a/artmura-site/public/styles.css
+++ b/artmura-site/public/styles.css
@@ -117,8 +117,8 @@ body.scrolled .corner{opacity:0;pointer-events:none}
 .card .meta{display:flex;justify-content:space-between;align-items:baseline;margin-top:7px}
 .card .col{font-size:12px;color:var(--ink-soft);display:inline-flex;align-items:center;gap:6px}
 .card .col .dot{width:9px;height:9px;border-radius:50%;border:1px solid rgba(0,0,0,.12);flex:0 0 auto}
-.card .pr{font-size:13px;font-variant-numeric:tabular-nums;color:var(--ink)}
-.card .pr small{color:var(--ink-soft);font-size:10px;letter-spacing:.06em}
+.card .pr{font-size:11px;letter-spacing:.12em;text-transform:uppercase;color:var(--gold);transition:.2s}
+.card:hover .pr{color:var(--accent)}
 
 /* ---------- footer ---------- */
 footer{border-top:1px solid var(--line);background:var(--paper)}
@@ -148,6 +148,19 @@ footer{border-top:1px solid var(--line);background:var(--paper)}
 .cta:hover{background:var(--accent)}
 .cta.ghost{background:transparent;color:var(--ink);border:1px solid var(--line);margin-top:10px}
 .cta.ghost:hover{border-color:var(--ink)}
+/* social share — shares the real DW product page */
+.share{display:flex;align-items:center;gap:10px;margin-top:22px;flex-wrap:wrap}
+.share-lbl{font-size:10px;letter-spacing:.2em;text-transform:uppercase;color:var(--ink-soft);margin-right:2px}
+.sbtn{display:inline-flex;align-items:center;justify-content:center;width:38px;height:38px;border-radius:50%;
+  border:1px solid var(--line);background:var(--paper);color:var(--ink-soft);cursor:pointer;transition:.2s;padding:0}
+.sbtn:hover{color:#fff;transform:translateY(-2px)}
+.sbtn.pin:hover{background:#e60023;border-color:#e60023}
+.sbtn.fb:hover{background:#1877f2;border-color:#1877f2}
+.sbtn.x:hover{background:#000;border-color:#000}
+.sbtn.mail:hover{background:var(--accent);border-color:var(--accent)}
+.sbtn.copy:hover{background:var(--ink);border-color:var(--ink)}
+.sbtn.copy.done{background:#3f8a5d;border-color:#3f8a5d;color:#fff}
+
 .spec{margin-top:30px;border-top:1px solid var(--line)}
 .spec .row{display:flex;justify-content:space-between;padding:11px 0;border-bottom:1px solid var(--line);font-size:13px}
 .spec .row .k{color:var(--ink-soft);letter-spacing:.04em}
diff --git a/scripts/artmura-onboard/apply-markup.js b/scripts/artmura-onboard/apply-markup.js
new file mode 100644
index 0000000..4bb7132
--- /dev/null
+++ b/scripts/artmura-onboard/apply-markup.js
@@ -0,0 +1,83 @@
+#!/usr/bin/env node
+/**
+ * Artmura retail markup — DW sell price = round(Cost / 0.75 / 0.85) to whole dollar.
+ * Cost basis = price_newwall_retail captured at onboard (immutable → idempotent re-runs).
+ * Writes the YARD variant price on the live store; Sample stays $5 (sample policy).
+ *
+ *   node apply-markup.js            # DRY RUN (no writes)
+ *   node apply-markup.js --apply    # write live
+ *
+ * Env: SHOPIFY_ADMIN_TOKEN (required), SHOPIFY_STORE (default sandbox=prod)
+ */
+const fs = require('fs');
+const path = require('path');
+
+const STORE = process.env.SHOPIFY_STORE || 'designer-laboratory-sandbox.myshopify.com';
+const TOKEN = process.env.SHOPIFY_ADMIN_TOKEN;
+const API = '2024-10';
+const APPLY = process.argv.includes('--apply');
+if (!TOKEN) { console.error('SHOPIFY_ADMIN_TOKEN required'); process.exit(1); }
+
+const DATA = path.join(__dirname, 'data', 'artmura.json');
+const HANDLES = path.join(__dirname, 'data', 'artmura-store-handles.json');
+const markup = cost => Math.round(cost / 0.75 / 0.85);   // whole dollar
+const isSample = v => (v.option1 || '').toLowerCase() === 'sample' || /-sample$/i.test(v.sku || '');
+
+const api = (p, opts = {}) => fetch(`https://${STORE}/admin/api/${API}${p}`, {
+  ...opts, headers: { 'X-Shopify-Access-Token': TOKEN, 'Content-Type': 'application/json', ...(opts.headers || {}) },
+});
+
+async function getAllArtmura() {
+  let url = `/products.json?vendor=Artmura&status=active&limit=250&fields=id,handle,title,variants`;
+  const out = [];
+  while (url) {
+    const res = await api(url);
+    const link = res.headers.get('Link') || '';
+    out.push(...((await res.json()).products || []));
+    const m = link.split(',').find(s => s.includes('rel="next"'));
+    url = m ? m.slice(m.indexOf('<') + 1, m.indexOf('>')).replace(/^https:\/\/[^/]+\/admin\/api\/[^/]+/, '') : null;
+  }
+  return out;
+}
+
+(async () => {
+  const d = JSON.parse(fs.readFileSync(DATA, 'utf8'));
+  const handleMap = JSON.parse(fs.readFileSync(HANDLES, 'utf8'));      // {MFR_SKU: dw_handle}
+  const mfrByHandle = Object.fromEntries(Object.entries(handleMap).map(([sku, h]) => [h, sku.toUpperCase()]));
+  const costByMfr = {};
+  d.products.forEach(r => { costByMfr[(r.mfr_sku || '').toUpperCase()] = Number(r.price_newwall_retail) || 0; });
+
+  const live = await getAllArtmura();
+  console.log(`live Artmura products: ${live.length}  |  mode: ${APPLY ? 'APPLY (writing)' : 'DRY RUN'}\n`);
+
+  let matched = 0, toChange = 0, unmatched = [], changes = [];
+  for (const p of live) {
+    const mfr = mfrByHandle[p.handle];
+    const cost = mfr ? costByMfr[mfr] : null;
+    const yard = p.variants.find(v => !isSample(v));
+    if (!mfr || !cost || !yard) { unmatched.push(p.handle); continue; }
+    matched++;
+    const want = markup(cost);
+    const cur = Math.round(Number(yard.price));
+    if (cur !== want) { toChange++; changes.push({ pid: p.id, vid: yard.id, handle: p.handle, mfr, cost, cur: yard.price, want }); }
+  }
+
+  console.log(`matched ${matched}/${live.length}  |  need price change: ${toChange}  |  unmatched: ${unmatched.length}`);
+  if (unmatched.length) console.log('UNMATCHED:', unmatched.slice(0, 10).join(', '), unmatched.length > 10 ? `… +${unmatched.length - 10}` : '');
+  console.log('\nfirst 8 changes:');
+  changes.slice(0, 8).forEach(c => console.log(`  ${c.mfr.padEnd(12)} $${c.cur} → $${c.want}   ${c.handle}`));
+
+  if (!APPLY) { console.log(`\nDRY RUN — would update ${toChange} Yard variants. Re-run with --apply to write.`); return; }
+
+  console.log(`\nwriting ${toChange} Yard prices...`);
+  let ok = 0, fail = 0;
+  for (const c of changes) {
+    try {
+      const res = await api(`/variants/${c.vid}.json`, { method: 'PUT', body: JSON.stringify({ variant: { id: c.vid, price: String(c.want) } }) });
+      if (res.ok) { ok++; if (ok % 25 === 0) console.log(`  …${ok}/${toChange}`); }
+      else { fail++; console.log(`  FAIL ${c.mfr} ${res.status}: ${(await res.text()).slice(0, 120)}`); }
+    } catch (e) { fail++; console.log(`  ERR ${c.mfr}: ${e.message}`); }
+    await new Promise(r => setTimeout(r, 120));   // ~8/s, under 2-bucket leak
+  }
+  console.log(`\nDONE — ${ok} updated, ${fail} failed.`);
+})().catch(e => { console.error(e); process.exit(1); });

← 700117a Artmura landing: Shop links now land on the real /collection  ·  back to Dw Yolo Loop  ·  Artmura landing: add Playwright component click-through (39/ 4ec71ec →