← 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
M artmura-site/public/index.htmlM artmura-site/public/product.htmlM artmura-site/public/styles.cssA scripts/artmura-onboard/apply-markup.js
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 & 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 & 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 →