[object Object]

← back to Flock Fix Viewer

board: show sqft S/R+D/R + specs-verified chips; pin port 61438; sqft + St Moritz enrichment tools

5ac880f5843822569e19067828866a28eff70a07 · 2026-09-02 14:01:03 -0700 · Steve

Files touched

Diff

commit 5ac880f5843822569e19067828866a28eff70a07
Author: Steve <steve@designerwallcoverings.com>
Date:   Wed Sep 2 14:01:03 2026 -0700

    board: show sqft S/R+D/R + specs-verified chips; pin port 61438; sqft + St Moritz enrichment tools
---
 .port                                              |   2 +-
 add-sqft.mjs                                       |  58 ++++++
 astek-vv.py                                        |  37 ++++
 fix-unit.mjs                                       |  43 +++++
 priceup-flock.mjs                                  |  99 ++++++++++
 ...ocked-velvet-memo-sample-phillipe-romano-2.json |  34 ++++
 ...ocked-velvet-memo-sample-phillipe-romano-3.json |  34 ++++
 ...ocked-velvet-memo-sample-phillipe-romano-1.json |  34 ++++
 ...ocked-velvet-memo-sample-phillipe-romano-3.json |  34 ++++
 ...ocked-velvet-memo-sample-phillipe-romano-2.json |  34 ++++
 ...ocked-velvet-memo-sample-phillipe-romano-3.json |  34 ++++
 ...flocked-velvet-memo-sample-phillipe-romano.json |  34 ++++
 ...ocked-velvet-memo-sample-phillipe-romano-1.json |  34 ++++
 ...ocked-velvet-memo-sample-phillipe-romano-2.json |  34 ++++
 ...ocked-velvet-memo-sample-phillipe-romano-3.json |  34 ++++
 ...ocked-velvet-memo-sample-phillipe-romano-4.json |  34 ++++
 ...ocked-velvet-memo-sample-phillipe-romano-1.json |  34 ++++
 ...ocked-velvet-memo-sample-phillipe-romano-3.json |  34 ++++
 ...ocked-velvet-memo-sample-phillipe-romano-2.json |  34 ++++
 ...ocked-velvet-memo-sample-phillipe-romano-3.json |  34 ++++
 .../katie-damask-white-on-silver-print-7034.json   |  38 ++++
 ...allpaper-black-on-black-closeup-flock-7026.json |  38 ++++
 .../nakura-flamingos-wallpaper-1-jta-48181b.json   |  38 ++++
 .../nakura-flamingos-wallpaper-2-jta-48221a.json   |  38 ++++
 ...ocked-velvet-memo-sample-phillipe-romano-1.json |  34 ++++
 ...ocked-velvet-memo-sample-phillipe-romano-2.json |  34 ++++
 ...flocked-velvet-memo-sample-phillipe-romano.json |  34 ++++
 ...ocked-velvet-memo-sample-phillipe-romano-2.json |  34 ++++
 ...ocked-velvet-memo-sample-phillipe-romano-3.json |  34 ++++
 ...flocked-velvet-memo-sample-phillipe-romano.json |  34 ++++
 ...ocked-velvet-memo-sample-phillipe-romano-1.json |  34 ++++
 ...ocked-velvet-memo-sample-phillipe-romano-4.json |  34 ++++
 ...flocked-velvet-memo-sample-phillipe-romano.json |  34 ++++
 public/index.html                                  |   2 +
 reverse-june16.mjs                                 |  69 +++++++
 server.js                                          |  10 +-
 theme-backup/product-description-meta.liquid       | 215 +++++++++++++++++++++
 37 files changed, 1501 insertions(+), 2 deletions(-)

diff --git a/.port b/.port
index c3e7ce5..245b96a 100644
--- a/.port
+++ b/.port
@@ -1 +1 @@
-61438
\ No newline at end of file
+51949
\ No newline at end of file
diff --git a/add-sqft.mjs b/add-sqft.mjs
new file mode 100644
index 0000000..4a57332
--- /dev/null
+++ b/add-sqft.mjs
@@ -0,0 +1,58 @@
+// Add square footage per single roll + double roll to metafields, computed from each
+// product's real width x length. Standing pattern for all roll-sold items. Reversible + ledgered.
+import fs from 'fs'; import os from 'os'; import path from 'path';
+const SHOP='designer-laboratory-sandbox.myshopify.com';
+const TOKEN=fs.readFileSync(new URL('./.token',import.meta.url),'utf8').trim();
+const LEDGER=path.join(os.homedir(),'.claude/yolo-queue/executed-reversible/ledger.jsonl');
+const DRY=process.argv.includes('--dry');
+
+async function gql(q,v={}){const r=await fetch(`https://${SHOP}/admin/api/2024-10/graphql.json`,{method:'POST',headers:{'X-Shopify-Access-Token':TOKEN,'Content-Type':'application/json'},body:JSON.stringify({query:q,variables:v})});return r.json();}
+const sleep=ms=>new Promise(r=>setTimeout(r,ms));
+const Q=`query($c:String){ products(first:100, query:"(tag:'Flock Velvet' OR title:flock OR handle:flock)", after:$c){
+  pageInfo{hasNextPage endCursor}
+  edges{node{ id handle status
+    variants(first:6){edges{node{title price}}}
+    w: metafield(namespace:"global",key:"width"){value}
+    l: metafield(namespace:"global",key:"length"){value}
+    sr: metafield(namespace:"global",key:"sqft_single_roll"){value}
+  }}
+}}`;
+const SETMF=`mutation($mf:[MetafieldsSetInput!]!){ metafieldsSet(metafields:$mf){ userErrors{field message} } }`;
+const num=s=>{const m=String(s||'').match(/([\d.]+)/);return m?parseFloat(m[1]):null;};
+const r2=n=>Math.round(n*100)/100;
+
+(async()=>{
+  let cur=null,all=[];
+  do{const r=await gql(Q,{c:cur});const p=r?.data?.products;if(!p){console.error(JSON.stringify(r));break;}all=all.concat(p.edges);cur=p.pageInfo.hasNextPage?p.pageInfo.endCursor:null;}while(cur);
+  const targets=all.map(e=>e.node).filter(n=>{
+    if(n.status==='ARCHIVED') return false;
+    const priced=n.variants.edges.map(e=>e.node).some(v=>!/sample/i.test(v.title||'')&&parseFloat(v.price)>10);
+    return priced && num(n.w?.value) && num(n.l?.value);   // need width + length to compute
+  });
+  console.log(`priced flock with width+length: ${targets.length}`);
+  let done=0;
+  for(const n of targets){
+    const widthIn=num(n.w?.value);          // 27
+    const drYd=num(n.l?.value);              // 5.5 (double roll length)
+    const srYd=drYd/2;                       // 2.75
+    const sqSR=r2((widthIn/12)*(srYd*3));    // 2.25 * 8.25 = 18.56
+    const sqDR=r2((widthIn/12)*(drYd*3));    // 2.25 * 16.5 = 37.13
+    const vSR=`${sqSR} Sq Ft`, vDR=`${sqDR} Sq Ft`;
+    if(DRY){ if(done<8) console.log(`  ${n.handle.slice(0,40)}  W=${widthIn}" DR=${drYd}yd -> S/R ${vSR} | D/R ${vDR}`); done++; continue; }
+    const mf=[
+      {ownerId:n.id,namespace:'global',key:'sqft_single_roll',type:'single_line_text_field',value:vSR},
+      {ownerId:n.id,namespace:'global',key:'sqft_double_roll',type:'single_line_text_field',value:vDR},
+      {ownerId:n.id,namespace:'custom',key:'sqft_single_roll',type:'single_line_text_field',value:vSR},
+      {ownerId:n.id,namespace:'custom',key:'sqft_double_roll',type:'single_line_text_field',value:vDR}
+    ];
+    const r=await gql(SETMF,{mf});
+    const errs=r?.data?.metafieldsSet?.userErrors;
+    if(errs&&errs.length){console.log('  FAIL',n.handle,JSON.stringify(errs));continue;}
+    done++;
+    fs.appendFileSync(LEDGER,JSON.stringify({ts:new Date().toISOString(),agent:'main:flock-sqft',ticket:'flock-sqft',
+      action:`add sqft S/R ${vSR} + D/R ${vDR} to ${n.handle}`,blast_radius:1,
+      undo_cmd:`delete metafields global/custom.sqft_single_roll & sqft_double_roll on ${n.id}`,verify:`sqft metafields present`})+'\n');
+    await sleep(220);
+  }
+  console.log(`${DRY?'DRY':'DONE'} · ${done}`);
+})();
diff --git a/astek-vv.py b/astek-vv.py
new file mode 100644
index 0000000..3e21a96
--- /dev/null
+++ b/astek-vv.py
@@ -0,0 +1,37 @@
+import urllib.request, json, re, html
+vv = []
+for pg in range(1, 40):
+    try:
+        req = urllib.request.Request(f'https://www.astek.com/products.json?limit=250&page={pg}',
+                                     headers={'User-Agent': 'Mozilla/5.0'})
+        prods = json.load(urllib.request.urlopen(req, timeout=25)).get('products', [])
+        if not prods:
+            break
+        for p in prods:
+            m = re.match(r'\s*(VV\d+)\b', p['title'])
+            if m:
+                vv.append((m.group(1), p))
+    except Exception as e:
+        break
+
+def txt(p):
+    b = html.unescape(re.sub(r'<[^>]+>', ' ', p.get('body_html', '')))
+    return re.sub(r'\s+', ' ', b)
+
+def spec(p, kw):
+    m = re.search(kw + r'[^0-9]*([\d.]+)\s*(?:inch|in|")', txt(p), re.I)
+    return m.group(1) if m else '?'
+
+def soldby(p):
+    m = re.search(r'Sold By ([A-Za-z ]+?)(?:Motif|Substrate|Vertical|$)', txt(p), re.I)
+    return m.group(1).strip() if m else '?'
+
+print('Astek VV-flocked products:', len(vv))
+for code, p in sorted(vv, key=lambda x: x[0]):
+    title = p['title'][:38]
+    pw = spec(p, 'Printed width')
+    sb = soldby(p)
+    nc = len(p['variants'])
+    pr = p['variants'][0]['price']
+    skus = ','.join((v.get('sku') or '') for v in p['variants'][:2])
+    print(f'  {code} | {title:38} | W={pw}" | soldBy={sb:14} | colorways={nc} | ${pr} | sku={skus[:30]}')
diff --git a/fix-unit.mjs b/fix-unit.mjs
new file mode 100644
index 0000000..b575787
--- /dev/null
+++ b/fix-unit.mjs
@@ -0,0 +1,43 @@
+// Fix flock unit_of_measure: single roll (NOT double), ships untrimmed. Reversible + ledgered.
+import fs from 'fs'; import os from 'os'; import path from 'path';
+const SHOP='designer-laboratory-sandbox.myshopify.com';
+const TOKEN=fs.readFileSync(new URL('./.token',import.meta.url),'utf8').trim();
+const LEDGER=path.join(os.homedir(),'.claude/yolo-queue/executed-reversible/ledger.jsonl');
+const DRY=process.argv.includes('--dry');
+const NEWVAL='Priced Per Single Roll. Ships untrimmed (30" untrimmed / 27" trimmed width).';
+
+async function gql(q,v={}){const r=await fetch(`https://${SHOP}/admin/api/2024-10/graphql.json`,{method:'POST',headers:{'X-Shopify-Access-Token':TOKEN,'Content-Type':'application/json'},body:JSON.stringify({query:q,variables:v})});return r.json();}
+const sleep=ms=>new Promise(r=>setTimeout(r,ms));
+const Q=`query($c:String){ products(first:100, query:"(tag:'Flock Velvet' OR title:flock OR handle:flock)", after:$c){
+  pageInfo{hasNextPage endCursor}
+  edges{node{ id handle status variants(first:6){edges{node{title price}}}
+    uom: metafield(namespace:"global",key:"unit_of_measure"){value} }}
+}}`;
+const SETMF=`mutation($mf:[MetafieldsSetInput!]!){ metafieldsSet(metafields:$mf){ userErrors{field message} } }`;
+
+(async()=>{
+  let cur=null,all=[];
+  do{const r=await gql(Q,{c:cur});const p=r?.data?.products;if(!p){console.error(JSON.stringify(r));break;}all=all.concat(p.edges);cur=p.pageInfo.hasNextPage?p.pageInfo.endCursor:null;}while(cur);
+  const targets=all.map(e=>e.node).filter(n=>{
+    if(n.status==='ARCHIVED') return false;
+    const roll=n.variants.edges.map(e=>e.node).filter(v=>!/sample/i.test(v.title||''));
+    const priced=roll.some(v=>parseFloat(v.price)>10);
+    const cur=(n.uom&&n.uom.value)||'';
+    return priced && /double/i.test(cur);   // only ones still saying "double"
+  });
+  console.log(`priced flock with 'double roll' language: ${targets.length}`);
+  if(DRY){targets.slice(0,10).forEach(n=>console.log('  ',n.handle,'::',n.uom.value));return;}
+  let done=0;
+  for(const n of targets){
+    const prior=(n.uom&&n.uom.value)||'';
+    const r=await gql(SETMF,{mf:[{ownerId:n.id,namespace:'global',key:'unit_of_measure',type:'single_line_text_field',value:NEWVAL}]});
+    const errs=r?.data?.metafieldsSet?.userErrors;
+    if(errs&&errs.length){console.log('  FAIL',n.handle,JSON.stringify(errs));continue;}
+    done++;
+    fs.appendFileSync(LEDGER,JSON.stringify({ts:new Date().toISOString(),agent:'main:flock-unit-fix',ticket:'flock-single-roll',
+      action:`unit_of_measure -> single roll (not double) on ${n.handle}`,blast_radius:1,
+      undo_cmd:`metafieldsSet global.unit_of_measure = ${JSON.stringify(prior)}`,verify:`uom no longer says 'double'`})+'\n');
+    await sleep(250);
+  }
+  console.log(`DONE · fixed ${done}`);
+})();
diff --git a/priceup-flock.mjs b/priceup-flock.mjs
new file mode 100644
index 0000000..b460944
--- /dev/null
+++ b/priceup-flock.mjs
@@ -0,0 +1,99 @@
+// Price up 27"-wide flock products into 2-variant (roll $212.99 + sample $4.25) products,
+// modeled on Naya's Zodiac. Snapshot + ledger each. Modes: --dry | --pilot | --apply
+import fs from 'fs'; import os from 'os'; import path from 'path';
+const SHOP='designer-laboratory-sandbox.myshopify.com';
+const TOKEN=fs.readFileSync(new URL('./.token',import.meta.url),'utf8').trim();
+const LEDGER=path.join(os.homedir(),'.claude/yolo-queue/executed-reversible/ledger.jsonl');
+const SNAPDIR=new URL('./priceup-snapshots/',import.meta.url);
+const MODE=process.argv.includes('--apply')?'apply':(process.argv.includes('--pilot')?'pilot':'dry');
+const ROLL_OPT='Sold per single roll (27" x 5.5 yards)';
+const ROLL_PRICE='212.99', SAMPLE_PRICE='4.25';
+try{fs.mkdirSync(SNAPDIR,{recursive:true});}catch(e){}
+
+async function gql(query,variables={}){
+  const r=await fetch(`https://${SHOP}/admin/api/2024-10/graphql.json`,{method:'POST',
+    headers:{'X-Shopify-Access-Token':TOKEN,'Content-Type':'application/json'},body:JSON.stringify({query,variables})});
+  return r.json();
+}
+const sleep=ms=>new Promise(r=>setTimeout(r,ms));
+const Q=`query($c:String){ products(first:100, query:"(tag:'Flock Velvet' OR title:flock OR handle:flock)", after:$c){
+  pageInfo{hasNextPage endCursor}
+  edges{node{ id handle title status
+    options{name values}
+    variants(first:10){edges{node{id sku title price selectedOptions{name value}}}}
+    wGlobal: metafield(namespace:"global",key:"width"){value}
+    wCustom: metafield(namespace:"custom",key:"width"){value}
+  }}
+}}`;
+const norm=t=>(t||'').toLowerCase().replace(/\s*\|.*$/,'').replace(/memo sample|flocked velvet|flock velvet|wallcovering|wallpaper|\bmemo\b|\bsample\b/g,'').replace(/[^a-z0-9 ]/g,' ').replace(/\s+/g,' ').trim();
+function widthInches(n){
+  const w=(n.wGlobal&&n.wGlobal.value)||(n.wCustom&&n.wCustom.value)||'';
+  const m=String(w).match(/([\d.]+)\s*inch/i); if(m) return parseFloat(m[1]);
+  for(const e of n.variants.edges){const mm=(e.node.title||'').match(/\(([\d.]+)"/); if(mm) return parseFloat(mm[1]);}
+  return null;
+}
+function maxRoll(n){return n.variants.edges.map(e=>e.node).filter(v=>!/sample/i.test(v.title||'')).reduce((m,v)=>Math.max(m,parseFloat(v.price)||0),0);}
+
+const SET=`mutation($input:ProductSetInput!){ productSet(synchronous:true, input:$input){ product{id handle
+  variants(first:10){edges{node{sku title price}}}} userErrors{field message} } }`;
+
+function buildInput(n, rollSku, sampleSku){
+  return { id:n.id,
+    productType:'Wallcovering',
+    productOptions:[{name:'Size', values:[{name:ROLL_OPT},{name:'Sample'}]}],
+    variants:[
+      {optionValues:[{optionName:'Size',name:ROLL_OPT}], price:ROLL_PRICE, sku:rollSku, inventoryPolicy:'CONTINUE'},
+      {optionValues:[{optionName:'Size',name:'Sample'}], price:SAMPLE_PRICE, sku:sampleSku, inventoryPolicy:'CONTINUE'}
+    ],
+    metafields:[
+      {namespace:'global',key:'v_prods_quantity_order_min',type:'single_line_text_field',value:'2'},
+      {namespace:'global',key:'v_prods_quantity_order_units',type:'single_line_text_field',value:'2'},
+      {namespace:'global',key:'unit_of_measure',type:'single_line_text_field',value:'Priced Per Single Roll. Packaged in Double Rolls.'},
+      {namespace:'global',key:'width',type:'single_line_text_field',value:'27 Inches Wide'},
+      {namespace:'custom',key:'width',type:'single_line_text_field',value:'27 Inches Wide'},
+      {namespace:'global',key:'length',type:'single_line_text_field',value:'5.5 Yards'}
+    ]
+  };
+}
+
+(async()=>{
+  let cur=null,all=[];
+  do{const r=await gql(Q,{c:cur});const p=r?.data?.products;if(!p){console.error(JSON.stringify(r));break;}all=all.concat(p.edges);cur=p.pageInfo.hasNextPage?p.pageInfo.endCursor:null;}while(cur);
+  const nodes=all.map(e=>e.node);
+  // width map from priced twins (pattern -> inches)
+  const twinWidth={};
+  for(const n of nodes){ if(maxRoll(n)>10){ const w=widthInches(n); if(w) twinWidth[norm(n.title)]=w; } }
+  // targets: sample-only (no roll price) AND 27" wide (own width metafield/option OR twin)
+  const ACTIVE_ONLY = !process.argv.includes('--include-draft');
+  const targets=nodes.filter(n=>{
+    if(n.status==='ARCHIVED') return false;     // never touch archived
+    if(ACTIVE_ONLY && n.status!=='ACTIVE') return false; // ACTIVE only unless --include-draft
+    if(maxRoll(n)>10) return false;            // already priced -> skip
+    const w=widthInches(n) ?? twinWidth[norm(n.title)] ?? null;
+    return w===27;                              // 27" only; unknown width -> skipped
+  });
+  const skippedUnknown=nodes.filter(n=>maxRoll(n)<=10 && (widthInches(n)??twinWidth[norm(n.title)]??null)===null);
+  console.log(`flock=${nodes.length} · 27\" sample-only TARGETS=${targets.length} · skipped(width unknown)=${skippedUnknown.length}`);
+  if(MODE==='dry'){ targets.slice(0,15).forEach(n=>console.log('  target',n.status,n.handle)); return; }
+
+  const list = MODE==='pilot' ? targets.slice(0,1) : targets;
+  console.log(`${MODE.toUpperCase()} on ${list.length} product(s)`);
+  let done=0,failed=0;
+  for(const n of list){
+    fs.writeFileSync(new URL(n.handle.replace(/[^a-z0-9-]/gi,'_')+'.json',SNAPDIR), JSON.stringify(n,null,2));
+    const base=(n.variants.edges.map(e=>e.node).find(v=>v.sku)?.sku||n.handle).replace(/-sample$/i,'').replace(/-Sample$/,'');
+    const rollSku=base, sampleSku=base+'-Sample';
+    const r=await gql(SET,{input:buildInput(n,rollSku,sampleSku)});
+    const errs=r?.data?.productSet?.userErrors;
+    if(errs&&errs.length){failed++;console.log('  FAIL',n.handle,JSON.stringify(errs));continue;}
+    done++;
+    const vs=r.data.productSet.product.variants.edges.map(e=>e.node);
+    console.log('  OK',n.handle,'->',vs.map(v=>v.title+'=$'+v.price).join(' | '));
+    fs.appendFileSync(LEDGER,JSON.stringify({ts:new Date().toISOString(),agent:'main:priceup-flock',ticket:'flock-priceup-27',
+      action:`price up ${n.handle} to roll $212.99 + sample $4.25 (27" flock)`,blast_radius:1,
+      undo_cmd:`restore from priceup-snapshots/${n.handle}.json (prior variants/options)`,
+      verify:`product ${n.handle} has 2 variants roll $212.99 + sample $4.25`})+'\n');
+    await sleep(400);
+  }
+  console.log(`${MODE} DONE · ok=${done} failed=${failed}`);
+})();
diff --git a/priceup-snapshots/alexias-art-deco-fans-flocked-velvet-memo-sample-phillipe-romano-2.json b/priceup-snapshots/alexias-art-deco-fans-flocked-velvet-memo-sample-phillipe-romano-2.json
new file mode 100644
index 0000000..e2ea6a9
--- /dev/null
+++ b/priceup-snapshots/alexias-art-deco-fans-flocked-velvet-memo-sample-phillipe-romano-2.json
@@ -0,0 +1,34 @@
+{
+  "id": "gid://shopify/Product/7862955671603",
+  "handle": "alexias-art-deco-fans-flocked-velvet-memo-sample-phillipe-romano-2",
+  "title": "Alexia's Art Deco Fans Flocked Velvet Memo Sample | Phillipe Romano",
+  "status": "ACTIVE",
+  "options": [
+    {
+      "name": "Title",
+      "values": [
+        "Default Title"
+      ]
+    }
+  ],
+  "variants": {
+    "edges": [
+      {
+        "node": {
+          "id": "gid://shopify/ProductVariant/44442520322099",
+          "sku": "FLOCK-1041-sample",
+          "title": "Default Title",
+          "price": "4.25",
+          "selectedOptions": [
+            {
+              "name": "Title",
+              "value": "Default Title"
+            }
+          ]
+        }
+      }
+    ]
+  },
+  "wGlobal": null,
+  "wCustom": null
+}
\ No newline at end of file
diff --git a/priceup-snapshots/alexias-art-deco-fans-flocked-velvet-memo-sample-phillipe-romano-3.json b/priceup-snapshots/alexias-art-deco-fans-flocked-velvet-memo-sample-phillipe-romano-3.json
new file mode 100644
index 0000000..50d900c
--- /dev/null
+++ b/priceup-snapshots/alexias-art-deco-fans-flocked-velvet-memo-sample-phillipe-romano-3.json
@@ -0,0 +1,34 @@
+{
+  "id": "gid://shopify/Product/7862956425267",
+  "handle": "alexias-art-deco-fans-flocked-velvet-memo-sample-phillipe-romano-3",
+  "title": "Alexia's Art Deco Fans Flocked Velvet Memo Sample | Phillipe Romano",
+  "status": "ACTIVE",
+  "options": [
+    {
+      "name": "Title",
+      "values": [
+        "Default Title"
+      ]
+    }
+  ],
+  "variants": {
+    "edges": [
+      {
+        "node": {
+          "id": "gid://shopify/ProductVariant/44442521075763",
+          "sku": "FLOCK-1042-sample",
+          "title": "Default Title",
+          "price": "4.25",
+          "selectedOptions": [
+            {
+              "name": "Title",
+              "value": "Default Title"
+            }
+          ]
+        }
+      }
+    ]
+  },
+  "wGlobal": null,
+  "wCustom": null
+}
\ No newline at end of file
diff --git a/priceup-snapshots/blairs-empire-flocked-velvet-memo-sample-phillipe-romano-1.json b/priceup-snapshots/blairs-empire-flocked-velvet-memo-sample-phillipe-romano-1.json
new file mode 100644
index 0000000..faa34b0
--- /dev/null
+++ b/priceup-snapshots/blairs-empire-flocked-velvet-memo-sample-phillipe-romano-1.json
@@ -0,0 +1,34 @@
+{
+  "id": "gid://shopify/Product/7862956097587",
+  "handle": "blairs-empire-flocked-velvet-memo-sample-phillipe-romano-1",
+  "title": "Blair's Empire Flocked Velvet Memo Sample | Phillipe Romano",
+  "status": "ACTIVE",
+  "options": [
+    {
+      "name": "Title",
+      "values": [
+        "Default Title"
+      ]
+    }
+  ],
+  "variants": {
+    "edges": [
+      {
+        "node": {
+          "id": "gid://shopify/ProductVariant/44442520748083",
+          "sku": "FLOCK-1052-sample",
+          "title": "Default Title",
+          "price": "4.25",
+          "selectedOptions": [
+            {
+              "name": "Title",
+              "value": "Default Title"
+            }
+          ]
+        }
+      }
+    ]
+  },
+  "wGlobal": null,
+  "wCustom": null
+}
\ No newline at end of file
diff --git a/priceup-snapshots/blairs-empire-flocked-velvet-memo-sample-phillipe-romano-3.json b/priceup-snapshots/blairs-empire-flocked-velvet-memo-sample-phillipe-romano-3.json
new file mode 100644
index 0000000..0d8ce27
--- /dev/null
+++ b/priceup-snapshots/blairs-empire-flocked-velvet-memo-sample-phillipe-romano-3.json
@@ -0,0 +1,34 @@
+{
+  "id": "gid://shopify/Product/7862957211699",
+  "handle": "blairs-empire-flocked-velvet-memo-sample-phillipe-romano-3",
+  "title": "Blair's Empire Flocked Velvet Memo Sample | Phillipe Romano",
+  "status": "ACTIVE",
+  "options": [
+    {
+      "name": "Title",
+      "values": [
+        "Default Title"
+      ]
+    }
+  ],
+  "variants": {
+    "edges": [
+      {
+        "node": {
+          "id": "gid://shopify/ProductVariant/44442522058803",
+          "sku": "FLOCK-1020-sample",
+          "title": "Default Title",
+          "price": "4.25",
+          "selectedOptions": [
+            {
+              "name": "Title",
+              "value": "Default Title"
+            }
+          ]
+        }
+      }
+    ]
+  },
+  "wGlobal": null,
+  "wCustom": null
+}
\ No newline at end of file
diff --git a/priceup-snapshots/daphenys-petite-heirloom-flocked-velvet-memo-sample-phillipe-romano-2.json b/priceup-snapshots/daphenys-petite-heirloom-flocked-velvet-memo-sample-phillipe-romano-2.json
new file mode 100644
index 0000000..0e650e3
--- /dev/null
+++ b/priceup-snapshots/daphenys-petite-heirloom-flocked-velvet-memo-sample-phillipe-romano-2.json
@@ -0,0 +1,34 @@
+{
+  "id": "gid://shopify/Product/7862956392499",
+  "handle": "daphenys-petite-heirloom-flocked-velvet-memo-sample-phillipe-romano-2",
+  "title": "Dapheny's Petite Heirloom Flocked Velvet Memo Sample | Phillipe Romano",
+  "status": "ACTIVE",
+  "options": [
+    {
+      "name": "Title",
+      "values": [
+        "Default Title"
+      ]
+    }
+  ],
+  "variants": {
+    "edges": [
+      {
+        "node": {
+          "id": "gid://shopify/ProductVariant/44442521042995",
+          "sku": "FLOCK-1007-sample",
+          "title": "Default Title",
+          "price": "4.25",
+          "selectedOptions": [
+            {
+              "name": "Title",
+              "value": "Default Title"
+            }
+          ]
+        }
+      }
+    ]
+  },
+  "wGlobal": null,
+  "wCustom": null
+}
\ No newline at end of file
diff --git a/priceup-snapshots/daphenys-petite-heirloom-flocked-velvet-memo-sample-phillipe-romano-3.json b/priceup-snapshots/daphenys-petite-heirloom-flocked-velvet-memo-sample-phillipe-romano-3.json
new file mode 100644
index 0000000..d782c46
--- /dev/null
+++ b/priceup-snapshots/daphenys-petite-heirloom-flocked-velvet-memo-sample-phillipe-romano-3.json
@@ -0,0 +1,34 @@
+{
+  "id": "gid://shopify/Product/7862956752947",
+  "handle": "daphenys-petite-heirloom-flocked-velvet-memo-sample-phillipe-romano-3",
+  "title": "Dapheny's Petite Heirloom Flocked Velvet Memo Sample | Phillipe Romano",
+  "status": "ACTIVE",
+  "options": [
+    {
+      "name": "Title",
+      "values": [
+        "Default Title"
+      ]
+    }
+  ],
+  "variants": {
+    "edges": [
+      {
+        "node": {
+          "id": "gid://shopify/ProductVariant/44442521370675",
+          "sku": "FLOCK-1008-sample",
+          "title": "Default Title",
+          "price": "4.25",
+          "selectedOptions": [
+            {
+              "name": "Title",
+              "value": "Default Title"
+            }
+          ]
+        }
+      }
+    ]
+  },
+  "wGlobal": null,
+  "wCustom": null
+}
\ No newline at end of file
diff --git a/priceup-snapshots/daphenys-petite-heirloom-flocked-velvet-memo-sample-phillipe-romano.json b/priceup-snapshots/daphenys-petite-heirloom-flocked-velvet-memo-sample-phillipe-romano.json
new file mode 100644
index 0000000..7eff042
--- /dev/null
+++ b/priceup-snapshots/daphenys-petite-heirloom-flocked-velvet-memo-sample-phillipe-romano.json
@@ -0,0 +1,34 @@
+{
+  "id": "gid://shopify/Product/7862954164275",
+  "handle": "daphenys-petite-heirloom-flocked-velvet-memo-sample-phillipe-romano",
+  "title": "Dapheny's Petite Heirloom Flocked Velvet Memo Sample | Phillipe Romano",
+  "status": "ACTIVE",
+  "options": [
+    {
+      "name": "Title",
+      "values": [
+        "Default Title"
+      ]
+    }
+  ],
+  "variants": {
+    "edges": [
+      {
+        "node": {
+          "id": "gid://shopify/ProductVariant/44442518847539",
+          "sku": "FLOCK-1009-sample",
+          "title": "Default Title",
+          "price": "4.25",
+          "selectedOptions": [
+            {
+              "name": "Title",
+              "value": "Default Title"
+            }
+          ]
+        }
+      }
+    ]
+  },
+  "wGlobal": null,
+  "wCustom": null
+}
\ No newline at end of file
diff --git a/priceup-snapshots/daphneys-heirloom-flocked-velvet-memo-sample-phillipe-romano-1.json b/priceup-snapshots/daphneys-heirloom-flocked-velvet-memo-sample-phillipe-romano-1.json
new file mode 100644
index 0000000..e93d09e
--- /dev/null
+++ b/priceup-snapshots/daphneys-heirloom-flocked-velvet-memo-sample-phillipe-romano-1.json
@@ -0,0 +1,34 @@
+{
+  "id": "gid://shopify/Product/7862954000435",
+  "handle": "daphneys-heirloom-flocked-velvet-memo-sample-phillipe-romano-1",
+  "title": "Daphney's Heirloom Flocked Velvet Memo Sample | Phillipe Romano",
+  "status": "ACTIVE",
+  "options": [
+    {
+      "name": "Title",
+      "values": [
+        "Default Title"
+      ]
+    }
+  ],
+  "variants": {
+    "edges": [
+      {
+        "node": {
+          "id": "gid://shopify/ProductVariant/44442518683699",
+          "sku": "FLOCK-1002-sample",
+          "title": "Default Title",
+          "price": "4.25",
+          "selectedOptions": [
+            {
+              "name": "Title",
+              "value": "Default Title"
+            }
+          ]
+        }
+      }
+    ]
+  },
+  "wGlobal": null,
+  "wCustom": null
+}
\ No newline at end of file
diff --git a/priceup-snapshots/daphneys-heirloom-flocked-velvet-memo-sample-phillipe-romano-2.json b/priceup-snapshots/daphneys-heirloom-flocked-velvet-memo-sample-phillipe-romano-2.json
new file mode 100644
index 0000000..75204b2
--- /dev/null
+++ b/priceup-snapshots/daphneys-heirloom-flocked-velvet-memo-sample-phillipe-romano-2.json
@@ -0,0 +1,34 @@
+{
+  "id": "gid://shopify/Product/7862954524723",
+  "handle": "daphneys-heirloom-flocked-velvet-memo-sample-phillipe-romano-2",
+  "title": "Daphney's Heirloom Flocked Velvet Memo Sample | Phillipe Romano",
+  "status": "ACTIVE",
+  "options": [
+    {
+      "name": "Title",
+      "values": [
+        "Default Title"
+      ]
+    }
+  ],
+  "variants": {
+    "edges": [
+      {
+        "node": {
+          "id": "gid://shopify/ProductVariant/44442519207987",
+          "sku": "FLOCK-1003-sample",
+          "title": "Default Title",
+          "price": "4.25",
+          "selectedOptions": [
+            {
+              "name": "Title",
+              "value": "Default Title"
+            }
+          ]
+        }
+      }
+    ]
+  },
+  "wGlobal": null,
+  "wCustom": null
+}
\ No newline at end of file
diff --git a/priceup-snapshots/daphneys-heirloom-flocked-velvet-memo-sample-phillipe-romano-3.json b/priceup-snapshots/daphneys-heirloom-flocked-velvet-memo-sample-phillipe-romano-3.json
new file mode 100644
index 0000000..fa9ed3a
--- /dev/null
+++ b/priceup-snapshots/daphneys-heirloom-flocked-velvet-memo-sample-phillipe-romano-3.json
@@ -0,0 +1,34 @@
+{
+  "id": "gid://shopify/Product/7862955212851",
+  "handle": "daphneys-heirloom-flocked-velvet-memo-sample-phillipe-romano-3",
+  "title": "Daphney's Heirloom Flocked Velvet Memo Sample | Phillipe Romano",
+  "status": "ACTIVE",
+  "options": [
+    {
+      "name": "Title",
+      "values": [
+        "Default Title"
+      ]
+    }
+  ],
+  "variants": {
+    "edges": [
+      {
+        "node": {
+          "id": "gid://shopify/ProductVariant/44442519863347",
+          "sku": "FLOCK-1005-sample",
+          "title": "Default Title",
+          "price": "4.25",
+          "selectedOptions": [
+            {
+              "name": "Title",
+              "value": "Default Title"
+            }
+          ]
+        }
+      }
+    ]
+  },
+  "wGlobal": null,
+  "wCustom": null
+}
\ No newline at end of file
diff --git a/priceup-snapshots/daphneys-heirloom-flocked-velvet-memo-sample-phillipe-romano-4.json b/priceup-snapshots/daphneys-heirloom-flocked-velvet-memo-sample-phillipe-romano-4.json
new file mode 100644
index 0000000..3509a12
--- /dev/null
+++ b/priceup-snapshots/daphneys-heirloom-flocked-velvet-memo-sample-phillipe-romano-4.json
@@ -0,0 +1,34 @@
+{
+  "id": "gid://shopify/Product/7862955540531",
+  "handle": "daphneys-heirloom-flocked-velvet-memo-sample-phillipe-romano-4",
+  "title": "Daphney's Heirloom Flocked Velvet Memo Sample | Phillipe Romano",
+  "status": "ACTIVE",
+  "options": [
+    {
+      "name": "Title",
+      "values": [
+        "Default Title"
+      ]
+    }
+  ],
+  "variants": {
+    "edges": [
+      {
+        "node": {
+          "id": "gid://shopify/ProductVariant/44442520191027",
+          "sku": "FLOCK-1004-sample",
+          "title": "Default Title",
+          "price": "4.25",
+          "selectedOptions": [
+            {
+              "name": "Title",
+              "value": "Default Title"
+            }
+          ]
+        }
+      }
+    ]
+  },
+  "wGlobal": null,
+  "wCustom": null
+}
\ No newline at end of file
diff --git a/priceup-snapshots/florences-st-moritz-flocked-velvet-memo-sample-phillipe-romano-1.json b/priceup-snapshots/florences-st-moritz-flocked-velvet-memo-sample-phillipe-romano-1.json
new file mode 100644
index 0000000..933fae3
--- /dev/null
+++ b/priceup-snapshots/florences-st-moritz-flocked-velvet-memo-sample-phillipe-romano-1.json
@@ -0,0 +1,34 @@
+{
+  "id": "gid://shopify/Product/7862954917939",
+  "handle": "florences-st-moritz-flocked-velvet-memo-sample-phillipe-romano-1",
+  "title": "Florence's St. Moritz Flocked Velvet Memo Sample | Phillipe Romano",
+  "status": "ACTIVE",
+  "options": [
+    {
+      "name": "Title",
+      "values": [
+        "Default Title"
+      ]
+    }
+  ],
+  "variants": {
+    "edges": [
+      {
+        "node": {
+          "id": "gid://shopify/ProductVariant/44442519568435",
+          "sku": "FLOCK-1024-sample",
+          "title": "Default Title",
+          "price": "4.25",
+          "selectedOptions": [
+            {
+              "name": "Title",
+              "value": "Default Title"
+            }
+          ]
+        }
+      }
+    ]
+  },
+  "wGlobal": null,
+  "wCustom": null
+}
\ No newline at end of file
diff --git a/priceup-snapshots/florences-st-moritz-flocked-velvet-memo-sample-phillipe-romano-3.json b/priceup-snapshots/florences-st-moritz-flocked-velvet-memo-sample-phillipe-romano-3.json
new file mode 100644
index 0000000..f90ea45
--- /dev/null
+++ b/priceup-snapshots/florences-st-moritz-flocked-velvet-memo-sample-phillipe-romano-3.json
@@ -0,0 +1,34 @@
+{
+  "id": "gid://shopify/Product/7862955835443",
+  "handle": "florences-st-moritz-flocked-velvet-memo-sample-phillipe-romano-3",
+  "title": "Florence's St. Moritz Flocked Velvet Memo Sample | Phillipe Romano",
+  "status": "ACTIVE",
+  "options": [
+    {
+      "name": "Title",
+      "values": [
+        "Default Title"
+      ]
+    }
+  ],
+  "variants": {
+    "edges": [
+      {
+        "node": {
+          "id": "gid://shopify/ProductVariant/44442520485939",
+          "sku": "FLOCK-1023-sample",
+          "title": "Default Title",
+          "price": "4.25",
+          "selectedOptions": [
+            {
+              "name": "Title",
+              "value": "Default Title"
+            }
+          ]
+        }
+      }
+    ]
+  },
+  "wGlobal": null,
+  "wCustom": null
+}
\ No newline at end of file
diff --git a/priceup-snapshots/gabbys-zebra-stripe-flocked-velvet-memo-sample-phillipe-romano-2.json b/priceup-snapshots/gabbys-zebra-stripe-flocked-velvet-memo-sample-phillipe-romano-2.json
new file mode 100644
index 0000000..ec42cee
--- /dev/null
+++ b/priceup-snapshots/gabbys-zebra-stripe-flocked-velvet-memo-sample-phillipe-romano-2.json
@@ -0,0 +1,34 @@
+{
+  "id": "gid://shopify/Product/7862957932595",
+  "handle": "gabbys-zebra-stripe-flocked-velvet-memo-sample-phillipe-romano-2",
+  "title": "Gabby's Zebra Stripe Flocked Velvet Memo Sample | Phillipe Romano",
+  "status": "ACTIVE",
+  "options": [
+    {
+      "name": "Title",
+      "values": [
+        "Default Title"
+      ]
+    }
+  ],
+  "variants": {
+    "edges": [
+      {
+        "node": {
+          "id": "gid://shopify/ProductVariant/44442522746931",
+          "sku": "FLOCK-1050-sample",
+          "title": "Default Title",
+          "price": "4.25",
+          "selectedOptions": [
+            {
+              "name": "Title",
+              "value": "Default Title"
+            }
+          ]
+        }
+      }
+    ]
+  },
+  "wGlobal": null,
+  "wCustom": null
+}
\ No newline at end of file
diff --git a/priceup-snapshots/gabbys-zebra-stripe-flocked-velvet-memo-sample-phillipe-romano-3.json b/priceup-snapshots/gabbys-zebra-stripe-flocked-velvet-memo-sample-phillipe-romano-3.json
new file mode 100644
index 0000000..bcb04a7
--- /dev/null
+++ b/priceup-snapshots/gabbys-zebra-stripe-flocked-velvet-memo-sample-phillipe-romano-3.json
@@ -0,0 +1,34 @@
+{
+  "id": "gid://shopify/Product/7862958260275",
+  "handle": "gabbys-zebra-stripe-flocked-velvet-memo-sample-phillipe-romano-3",
+  "title": "Gabby's Zebra Stripe Flocked Velvet Memo Sample | Phillipe Romano",
+  "status": "ACTIVE",
+  "options": [
+    {
+      "name": "Title",
+      "values": [
+        "Default Title"
+      ]
+    }
+  ],
+  "variants": {
+    "edges": [
+      {
+        "node": {
+          "id": "gid://shopify/ProductVariant/44442523140147",
+          "sku": "FLOCK-1051-sample",
+          "title": "Default Title",
+          "price": "4.25",
+          "selectedOptions": [
+            {
+              "name": "Title",
+              "value": "Default Title"
+            }
+          ]
+        }
+      }
+    ]
+  },
+  "wGlobal": null,
+  "wCustom": null
+}
\ No newline at end of file
diff --git a/priceup-snapshots/katie-damask-white-on-silver-print-7034.json b/priceup-snapshots/katie-damask-white-on-silver-print-7034.json
new file mode 100644
index 0000000..1d7a8ee
--- /dev/null
+++ b/priceup-snapshots/katie-damask-white-on-silver-print-7034.json
@@ -0,0 +1,38 @@
+{
+  "id": "gid://shopify/Product/1494875865200",
+  "handle": "katie-damask-white-on-silver-print-7034",
+  "title": "Katie Damask - White on Silver",
+  "status": "ACTIVE",
+  "options": [
+    {
+      "name": "Title",
+      "values": [
+        "Default Title"
+      ]
+    }
+  ],
+  "variants": {
+    "edges": [
+      {
+        "node": {
+          "id": "gid://shopify/ProductVariant/13898831822960",
+          "sku": "PRINT-7034-sample",
+          "title": "Default Title",
+          "price": "4.25",
+          "selectedOptions": [
+            {
+              "name": "Title",
+              "value": "Default Title"
+            }
+          ]
+        }
+      }
+    ]
+  },
+  "wGlobal": {
+    "value": "27 Inches Wide"
+  },
+  "wCustom": {
+    "value": "27 Inches Wide"
+  }
+}
\ No newline at end of file
diff --git a/priceup-snapshots/kelly-flocked-lion-wallpaper-black-on-black-closeup-flock-7026.json b/priceup-snapshots/kelly-flocked-lion-wallpaper-black-on-black-closeup-flock-7026.json
new file mode 100644
index 0000000..c7e5219
--- /dev/null
+++ b/priceup-snapshots/kelly-flocked-lion-wallpaper-black-on-black-closeup-flock-7026.json
@@ -0,0 +1,38 @@
+{
+  "id": "gid://shopify/Product/1494874587248",
+  "handle": "kelly-flocked-lion-wallpaper-black-on-black-closeup-flock-7026",
+  "title": "Kelly Flocked Lion Wallcovering - Black on Black",
+  "status": "ACTIVE",
+  "options": [
+    {
+      "name": "Title",
+      "values": [
+        "Default Title"
+      ]
+    }
+  ],
+  "variants": {
+    "edges": [
+      {
+        "node": {
+          "id": "gid://shopify/ProductVariant/13898808590448",
+          "sku": "Flock-7026-sample",
+          "title": "Default Title",
+          "price": "4.25",
+          "selectedOptions": [
+            {
+              "name": "Title",
+              "value": "Default Title"
+            }
+          ]
+        }
+      }
+    ]
+  },
+  "wGlobal": {
+    "value": "27 inches x 15 ft"
+  },
+  "wCustom": {
+    "value": "27 inches x 15 ft"
+  }
+}
\ No newline at end of file
diff --git a/priceup-snapshots/nakura-flamingos-wallpaper-1-jta-48181b.json b/priceup-snapshots/nakura-flamingos-wallpaper-1-jta-48181b.json
new file mode 100644
index 0000000..16e2280
--- /dev/null
+++ b/priceup-snapshots/nakura-flamingos-wallpaper-1-jta-48181b.json
@@ -0,0 +1,38 @@
+{
+  "id": "gid://shopify/Product/1497822986352",
+  "handle": "nakura-flamingos-wallpaper-1-jta-48181b",
+  "title": "Nakura Flamingos Wallcovering - 1",
+  "status": "ACTIVE",
+  "options": [
+    {
+      "name": "Size",
+      "values": [
+        "Sample"
+      ]
+    }
+  ],
+  "variants": {
+    "edges": [
+      {
+        "node": {
+          "id": "gid://shopify/ProductVariant/13932294340720",
+          "sku": "DWBW-600316-Sample",
+          "title": "Sample",
+          "price": "4.25",
+          "selectedOptions": [
+            {
+              "name": "Size",
+              "value": "Sample"
+            }
+          ]
+        }
+      }
+    ]
+  },
+  "wGlobal": {
+    "value": "27 Inches"
+  },
+  "wCustom": {
+    "value": "27 inches x 16.5ft"
+  }
+}
\ No newline at end of file
diff --git a/priceup-snapshots/nakura-flamingos-wallpaper-2-jta-48221a.json b/priceup-snapshots/nakura-flamingos-wallpaper-2-jta-48221a.json
new file mode 100644
index 0000000..0073d18
--- /dev/null
+++ b/priceup-snapshots/nakura-flamingos-wallpaper-2-jta-48221a.json
@@ -0,0 +1,38 @@
+{
+  "id": "gid://shopify/Product/1497844121712",
+  "handle": "nakura-flamingos-wallpaper-2-jta-48221a",
+  "title": "Nakura Flamingos Wallcovering - 2",
+  "status": "ACTIVE",
+  "options": [
+    {
+      "name": "Size",
+      "values": [
+        "Sample"
+      ]
+    }
+  ],
+  "variants": {
+    "edges": [
+      {
+        "node": {
+          "id": "gid://shopify/ProductVariant/13932412993648",
+          "sku": "DWBW-600325-Sample",
+          "title": "Sample",
+          "price": "4.25",
+          "selectedOptions": [
+            {
+              "name": "Size",
+              "value": "Sample"
+            }
+          ]
+        }
+      }
+    ]
+  },
+  "wGlobal": {
+    "value": "27 Inches"
+  },
+  "wCustom": {
+    "value": "27 inches x 16.5ft"
+  }
+}
\ No newline at end of file
diff --git a/priceup-snapshots/nayas-zodiac-flocked-velvet-memo-sample-phillipe-romano-1.json b/priceup-snapshots/nayas-zodiac-flocked-velvet-memo-sample-phillipe-romano-1.json
new file mode 100644
index 0000000..d065dee
--- /dev/null
+++ b/priceup-snapshots/nayas-zodiac-flocked-velvet-memo-sample-phillipe-romano-1.json
@@ -0,0 +1,34 @@
+{
+  "id": "gid://shopify/Product/7862955442227",
+  "handle": "nayas-zodiac-flocked-velvet-memo-sample-phillipe-romano-1",
+  "title": "Naya's Zodiac Flocked Velvet Memo Sample | Phillipe Romano",
+  "status": "ACTIVE",
+  "options": [
+    {
+      "name": "Title",
+      "values": [
+        "Default Title"
+      ]
+    }
+  ],
+  "variants": {
+    "edges": [
+      {
+        "node": {
+          "id": "gid://shopify/ProductVariant/44442520092723",
+          "sku": "FLOCK-1035-sample",
+          "title": "Default Title",
+          "price": "4.25",
+          "selectedOptions": [
+            {
+              "name": "Title",
+              "value": "Default Title"
+            }
+          ]
+        }
+      }
+    ]
+  },
+  "wGlobal": null,
+  "wCustom": null
+}
\ No newline at end of file
diff --git a/priceup-snapshots/nayas-zodiac-flocked-velvet-memo-sample-phillipe-romano-2.json b/priceup-snapshots/nayas-zodiac-flocked-velvet-memo-sample-phillipe-romano-2.json
new file mode 100644
index 0000000..abb642d
--- /dev/null
+++ b/priceup-snapshots/nayas-zodiac-flocked-velvet-memo-sample-phillipe-romano-2.json
@@ -0,0 +1,34 @@
+{
+  "id": "gid://shopify/Product/7862956228659",
+  "handle": "nayas-zodiac-flocked-velvet-memo-sample-phillipe-romano-2",
+  "title": "Naya's Zodiac Flocked Velvet Memo Sample | Phillipe Romano",
+  "status": "ACTIVE",
+  "options": [
+    {
+      "name": "Title",
+      "values": [
+        "Default Title"
+      ]
+    }
+  ],
+  "variants": {
+    "edges": [
+      {
+        "node": {
+          "id": "gid://shopify/ProductVariant/44442520879155",
+          "sku": "FLOCK-1036-sample",
+          "title": "Default Title",
+          "price": "4.25",
+          "selectedOptions": [
+            {
+              "name": "Title",
+              "value": "Default Title"
+            }
+          ]
+        }
+      }
+    ]
+  },
+  "wGlobal": null,
+  "wCustom": null
+}
\ No newline at end of file
diff --git a/priceup-snapshots/nayas-zodiac-flocked-velvet-memo-sample-phillipe-romano.json b/priceup-snapshots/nayas-zodiac-flocked-velvet-memo-sample-phillipe-romano.json
new file mode 100644
index 0000000..50cf839
--- /dev/null
+++ b/priceup-snapshots/nayas-zodiac-flocked-velvet-memo-sample-phillipe-romano.json
@@ -0,0 +1,34 @@
+{
+  "id": "gid://shopify/Product/7862954983475",
+  "handle": "nayas-zodiac-flocked-velvet-memo-sample-phillipe-romano",
+  "title": "Naya's Zodiac Flocked Velvet Memo Sample | Phillipe Romano",
+  "status": "ACTIVE",
+  "options": [
+    {
+      "name": "Title",
+      "values": [
+        "Default Title"
+      ]
+    }
+  ],
+  "variants": {
+    "edges": [
+      {
+        "node": {
+          "id": "gid://shopify/ProductVariant/44442519633971",
+          "sku": "FLOCK-1034-sample",
+          "title": "Default Title",
+          "price": "4.25",
+          "selectedOptions": [
+            {
+              "name": "Title",
+              "value": "Default Title"
+            }
+          ]
+        }
+      }
+    ]
+  },
+  "wGlobal": null,
+  "wCustom": null
+}
\ No newline at end of file
diff --git a/priceup-snapshots/nekos-griffons-flocked-velvet-memo-sample-phillipe-romano-2.json b/priceup-snapshots/nekos-griffons-flocked-velvet-memo-sample-phillipe-romano-2.json
new file mode 100644
index 0000000..ff685c6
--- /dev/null
+++ b/priceup-snapshots/nekos-griffons-flocked-velvet-memo-sample-phillipe-romano-2.json
@@ -0,0 +1,34 @@
+{
+  "id": "gid://shopify/Product/7862956032051",
+  "handle": "nekos-griffons-flocked-velvet-memo-sample-phillipe-romano-2",
+  "title": "Neko's Griffons Flocked Velvet Memo Sample | Phillipe Romano",
+  "status": "ACTIVE",
+  "options": [
+    {
+      "name": "Title",
+      "values": [
+        "Default Title"
+      ]
+    }
+  ],
+  "variants": {
+    "edges": [
+      {
+        "node": {
+          "id": "gid://shopify/ProductVariant/44442520682547",
+          "sku": "FLOCK-1030-sample",
+          "title": "Default Title",
+          "price": "4.25",
+          "selectedOptions": [
+            {
+              "name": "Title",
+              "value": "Default Title"
+            }
+          ]
+        }
+      }
+    ]
+  },
+  "wGlobal": null,
+  "wCustom": null
+}
\ No newline at end of file
diff --git a/priceup-snapshots/nekos-griffons-flocked-velvet-memo-sample-phillipe-romano-3.json b/priceup-snapshots/nekos-griffons-flocked-velvet-memo-sample-phillipe-romano-3.json
new file mode 100644
index 0000000..0ac3601
--- /dev/null
+++ b/priceup-snapshots/nekos-griffons-flocked-velvet-memo-sample-phillipe-romano-3.json
@@ -0,0 +1,34 @@
+{
+  "id": "gid://shopify/Product/7862957277235",
+  "handle": "nekos-griffons-flocked-velvet-memo-sample-phillipe-romano-3",
+  "title": "Neko's Griffons Flocked Velvet Memo Sample | Phillipe Romano",
+  "status": "ACTIVE",
+  "options": [
+    {
+      "name": "Title",
+      "values": [
+        "Default Title"
+      ]
+    }
+  ],
+  "variants": {
+    "edges": [
+      {
+        "node": {
+          "id": "gid://shopify/ProductVariant/44442522124339",
+          "sku": "FLOCK-1032-sample",
+          "title": "Default Title",
+          "price": "4.25",
+          "selectedOptions": [
+            {
+              "name": "Title",
+              "value": "Default Title"
+            }
+          ]
+        }
+      }
+    ]
+  },
+  "wGlobal": null,
+  "wCustom": null
+}
\ No newline at end of file
diff --git a/priceup-snapshots/nekos-griffons-flocked-velvet-memo-sample-phillipe-romano.json b/priceup-snapshots/nekos-griffons-flocked-velvet-memo-sample-phillipe-romano.json
new file mode 100644
index 0000000..45a4ccb
--- /dev/null
+++ b/priceup-snapshots/nekos-griffons-flocked-velvet-memo-sample-phillipe-romano.json
@@ -0,0 +1,34 @@
+{
+  "id": "gid://shopify/Product/7862955147315",
+  "handle": "nekos-griffons-flocked-velvet-memo-sample-phillipe-romano",
+  "title": "Neko's Griffons Flocked Velvet Memo Sample | Phillipe Romano",
+  "status": "ACTIVE",
+  "options": [
+    {
+      "name": "Title",
+      "values": [
+        "Default Title"
+      ]
+    }
+  ],
+  "variants": {
+    "edges": [
+      {
+        "node": {
+          "id": "gid://shopify/ProductVariant/44442519797811",
+          "sku": "FLOCK-1031-sample",
+          "title": "Default Title",
+          "price": "4.25",
+          "selectedOptions": [
+            {
+              "name": "Title",
+              "value": "Default Title"
+            }
+          ]
+        }
+      }
+    ]
+  },
+  "wGlobal": null,
+  "wCustom": null
+}
\ No newline at end of file
diff --git a/priceup-snapshots/veronicas-madison-flocked-velvet-memo-sample-phillipe-romano-1.json b/priceup-snapshots/veronicas-madison-flocked-velvet-memo-sample-phillipe-romano-1.json
new file mode 100644
index 0000000..2b59b2a
--- /dev/null
+++ b/priceup-snapshots/veronicas-madison-flocked-velvet-memo-sample-phillipe-romano-1.json
@@ -0,0 +1,34 @@
+{
+  "id": "gid://shopify/Product/7862954885171",
+  "handle": "veronicas-madison-flocked-velvet-memo-sample-phillipe-romano-1",
+  "title": "Veronica's Madison Flocked Velvet Memo Sample | Phillipe Romano",
+  "status": "ACTIVE",
+  "options": [
+    {
+      "name": "Title",
+      "values": [
+        "Default Title"
+      ]
+    }
+  ],
+  "variants": {
+    "edges": [
+      {
+        "node": {
+          "id": "gid://shopify/ProductVariant/44442519535667",
+          "sku": "FLOCK-1016-sample",
+          "title": "Default Title",
+          "price": "4.25",
+          "selectedOptions": [
+            {
+              "name": "Title",
+              "value": "Default Title"
+            }
+          ]
+        }
+      }
+    ]
+  },
+  "wGlobal": null,
+  "wCustom": null
+}
\ No newline at end of file
diff --git a/priceup-snapshots/veronicas-madison-flocked-velvet-memo-sample-phillipe-romano-4.json b/priceup-snapshots/veronicas-madison-flocked-velvet-memo-sample-phillipe-romano-4.json
new file mode 100644
index 0000000..51880b2
--- /dev/null
+++ b/priceup-snapshots/veronicas-madison-flocked-velvet-memo-sample-phillipe-romano-4.json
@@ -0,0 +1,34 @@
+{
+  "id": "gid://shopify/Product/7862956916787",
+  "handle": "veronicas-madison-flocked-velvet-memo-sample-phillipe-romano-4",
+  "title": "Veronica's Madison Flocked Velvet Memo Sample | Phillipe Romano",
+  "status": "ACTIVE",
+  "options": [
+    {
+      "name": "Title",
+      "values": [
+        "Default Title"
+      ]
+    }
+  ],
+  "variants": {
+    "edges": [
+      {
+        "node": {
+          "id": "gid://shopify/ProductVariant/44442521534515",
+          "sku": "FLOCK-1014-sample",
+          "title": "Default Title",
+          "price": "4.25",
+          "selectedOptions": [
+            {
+              "name": "Title",
+              "value": "Default Title"
+            }
+          ]
+        }
+      }
+    ]
+  },
+  "wGlobal": null,
+  "wCustom": null
+}
\ No newline at end of file
diff --git a/priceup-snapshots/veronicas-madison-flocked-velvet-memo-sample-phillipe-romano.json b/priceup-snapshots/veronicas-madison-flocked-velvet-memo-sample-phillipe-romano.json
new file mode 100644
index 0000000..b472172
--- /dev/null
+++ b/priceup-snapshots/veronicas-madison-flocked-velvet-memo-sample-phillipe-romano.json
@@ -0,0 +1,34 @@
+{
+  "id": "gid://shopify/Product/7862954360883",
+  "handle": "veronicas-madison-flocked-velvet-memo-sample-phillipe-romano",
+  "title": "Veronica's Madison Flocked Velvet Memo Sample | Phillipe Romano",
+  "status": "ACTIVE",
+  "options": [
+    {
+      "name": "Title",
+      "values": [
+        "Default Title"
+      ]
+    }
+  ],
+  "variants": {
+    "edges": [
+      {
+        "node": {
+          "id": "gid://shopify/ProductVariant/44442519044147",
+          "sku": "FLOCK-1015-sample",
+          "title": "Default Title",
+          "price": "4.25",
+          "selectedOptions": [
+            {
+              "name": "Title",
+              "value": "Default Title"
+            }
+          ]
+        }
+      }
+    ]
+  },
+  "wGlobal": null,
+  "wCustom": null
+}
\ No newline at end of file
diff --git a/public/index.html b/public/index.html
index f70898e..ef01b4b 100644
--- a/public/index.html
+++ b/public/index.html
@@ -75,6 +75,8 @@ function render(){
         +chip(r.f_buyable,'buyable','not buyable',false)
         +chip(r.f_min,'min set','no min',true)
         +chip(r.f_width,'width set','no width',true)
+        +chip(r.f_sqft,(r.sqftSR?('S/R '+r.sqftSR.replace(' Sq Ft','')+' · D/R '+(r.sqftDR||'').replace(' Sq Ft','')+' sqft'):'sqft set'),'no sqft',true)
+        +chip(r.f_specs,'specs verified','no specs',true)
         +(r.colorwayDup?'<span class="chip warn"><span class="dot" style="background:currentColor"></span>colorway: rename</span>':'')
       +'</div>'
       +'<div class="when" title="'+esc(r.created)+'">🕓 '+fmtWhen(r.created)+'</div>'
diff --git a/reverse-june16.mjs b/reverse-june16.mjs
new file mode 100644
index 0000000..5dff295
--- /dev/null
+++ b/reverse-june16.mjs
@@ -0,0 +1,69 @@
+// Reverse the June 16 2026 flock batch: ARCHIVE (reversible) all flock products
+// created 2026-06-15/16/17 that are sample-only ($4.25/no roll price). NOT delete.
+import fs from 'fs';
+import os from 'os';
+import path from 'path';
+
+const SHOP = 'designer-laboratory-sandbox.myshopify.com';
+const TOKEN = fs.readFileSync(new URL('./.token', import.meta.url), 'utf8').trim();
+const LEDGER = path.join(os.homedir(), '.claude/yolo-queue/executed-reversible/ledger.jsonl');
+const SNAP = new URL('./reverse-june16-snapshot.json', import.meta.url);
+const DRY = process.argv.includes('--dry');
+const DAYS = new Set(['2026-06-15', '2026-06-16', '2026-06-17']);
+
+async function gql(query, variables = {}) {
+  const r = await fetch(`https://${SHOP}/admin/api/2024-10/graphql.json`, {
+    method: 'POST', headers: { 'X-Shopify-Access-Token': TOKEN, 'Content-Type': 'application/json' },
+    body: JSON.stringify({ query, variables })
+  });
+  return r.json();
+}
+const sleep = ms => new Promise(r => setTimeout(r, ms));
+
+const Q = `query($c:String){
+  products(first:100, query:"(tag:'Flock Velvet' OR title:flock OR handle:flock)", after:$c){
+    pageInfo{hasNextPage endCursor}
+    edges{node{ id handle title status createdAt variants(first:6){edges{node{title price}}} }}
+  }
+}`;
+const ARCH = `mutation($id:ID!){ productUpdate(input:{id:$id, status:ARCHIVED}){ product{id status} userErrors{field message} } }`;
+
+(async () => {
+  let cur = null, all = [];
+  do {
+    const r = await gql(Q, { c: cur });
+    const p = r?.data?.products; if (!p) { console.error(JSON.stringify(r)); break; }
+    all = all.concat(p.edges);
+    cur = p.pageInfo.hasNextPage ? p.pageInfo.endCursor : null;
+  } while (cur);
+
+  const targets = all.map(e => e.node).filter(n => {
+    if (!DAYS.has((n.createdAt || '').slice(0, 10))) return false;
+    if (n.status === 'ARCHIVED') return false;                 // already archived
+    const roll = (n.variants.edges || []).map(v => v.node).filter(v => !/sample/i.test(v.title || ''));
+    const maxp = roll.reduce((m, v) => Math.max(m, parseFloat(v.price) || 0), 0);
+    return maxp <= 10;                                         // sample-only junk ONLY (safety guard)
+  });
+
+  const byStatus = targets.reduce((a, n) => (a[n.status] = (a[n.status] || 0) + 1, a), {});
+  console.log(`Scanned ${all.length} flock · targets to ARCHIVE: ${targets.length}`, byStatus);
+  fs.writeFileSync(SNAP, JSON.stringify(targets.map(n => ({ id: n.id, handle: n.handle, priorStatus: n.status })), null, 2));
+  if (DRY) { targets.slice(0, 10).forEach(n => console.log('  would archive', n.status, n.handle)); return; }
+
+  let done = 0, failed = 0;
+  for (const n of targets) {
+    const r = await gql(ARCH, { id: n.id });
+    const errs = r?.data?.productUpdate?.userErrors;
+    if (errs && errs.length) { failed++; console.log('  FAIL', n.handle, JSON.stringify(errs)); continue; }
+    done++;
+    fs.appendFileSync(LEDGER, JSON.stringify({
+      ts: new Date().toISOString(), agent: 'main:flock-reverse-june16', ticket: 'flock-reverse-june16',
+      action: `ARCHIVE ${n.handle} (was ${n.status}) — reverse June-16 sample-shell batch`, blast_radius: 1,
+      undo_cmd: `productUpdate ${n.id} status:${n.priorStatus}`,
+      verify: `product ${n.handle} status == ARCHIVED; gone from storefront`
+    }) + '\n');
+    if (done % 10 === 0) console.log(`  …${done}/${targets.length}`);
+    await sleep(300);
+  }
+  console.log(`DONE · archived ${done} · failed ${failed} · snapshot: reverse-june16-snapshot.json`);
+})();
diff --git a/server.js b/server.js
index 28b76d3..3d7c2ba 100644
--- a/server.js
+++ b/server.js
@@ -34,6 +34,10 @@ const PRODUCT_Q = `query($c:String){
       mMin: metafield(namespace:"global", key:"v_prods_quantity_order_min"){value}
       mWidth: metafield(namespace:"global", key:"width"){value}
       mUnit: metafield(namespace:"global", key:"unit_of_measure"){value}
+      mSqSR: metafield(namespace:"global", key:"sqft_single_roll"){value}
+      mSqDR: metafield(namespace:"global", key:"sqft_double_roll"){value}
+      mRepeat: metafield(namespace:"custom", key:"pattern_repeat"){value}
+      mPrint: metafield(namespace:"custom", key:"print_type"){value}
     }}
   }
 }`;
@@ -71,6 +75,10 @@ async function fetchStatus() {
       f_buyable: buyable,
       f_min: !!(n.mMin && n.mMin.value),
       f_width: !!(n.mWidth && n.mWidth.value),
+      f_sqft: !!(n.mSqSR && n.mSqSR.value && n.mSqDR && n.mSqDR.value),
+      f_specs: !!(n.mRepeat && n.mRepeat.value && n.mPrint && n.mPrint.value),
+      sqftSR: (n.mSqSR && n.mSqSR.value) || null,
+      sqftDR: (n.mSqDR && n.mSqDR.value) || null,
       colorwayDup: isDup,
       fixed: !hasQuotes && hasPrice, // primary fix complete
     };
@@ -92,7 +100,7 @@ const server = http.createServer(async (req, res) => {
   }
   res.writeHead(200, { 'Content-Type': 'text/html' }); res.end(PAGE);
 });
-server.listen(0, '127.0.0.1', () => {
+server.listen(process.env.PORT || 61438, '127.0.0.1', () => {
   const port = server.address().port;
   fs.writeFileSync(path.join(__dirname, '.port'), String(port));
   console.log('FLOCK-FIX-VIEWER listening on http://127.0.0.1:' + port + '  (admin / DW2024!)');
diff --git a/theme-backup/product-description-meta.liquid b/theme-backup/product-description-meta.liquid
new file mode 100644
index 0000000..ba8f7f9
--- /dev/null
+++ b/theme-backup/product-description-meta.liquid
@@ -0,0 +1,215 @@
+
+<style>.spec-note--quote{font-size:.85em;color:#6b6b6b;font-style:italic;margin-top:6px}</style>
+<style>
+.dw-specs { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; margin: 16px 0; }
+.dw-specs-title { font-size: 13px; font-weight: 600; color: #888; text-transform: uppercase; letter-spacing: 0.08em; margin-bottom: 8px; padding-bottom: 6px; border-bottom: 1px solid #eee; }
+.dw-spec-row { display: flex; padding: 6px 0; border-bottom: 1px solid #f5f5f5; font-size: 15px; line-height: 1.4; }
+.dw-spec-row:last-child { border-bottom: none; }
+.dw-spec-label { font-weight: 600; color: #333; width: 40%; min-width: 120px; flex-shrink: 0; }
+.dw-spec-value { color: #555; flex: 1; }
+/* Collapsed "more specifications" panel (Steve 2026-07-28): only Product Type,
+   Pattern Name, Color and SKU stay visible; everything else lives in here. */
+.dw-specs-more { margin: 0; }
+/* Steve TK-10095: darker/heavier summary so it no longer blends in + a rotating chevron toggle icon */
+.dw-specs-more > summary { cursor: pointer; list-style: none; font-size: 14px; font-weight: 700; color: #1a1a1a; text-transform: uppercase; letter-spacing: 0.05em; padding: 11px 0; display: flex; align-items: center; gap: 9px; -webkit-user-select: none; user-select: none; border-bottom: 1px solid #e3e3e3; }
+.dw-specs-more > summary::-webkit-details-marker { display: none; }
+.dw-specs-more > summary::before { content: ""; width: 7px; height: 7px; border-right: 2px solid currentColor; border-bottom: 2px solid currentColor; transform: rotate(-45deg); transition: transform 0.2s ease; flex-shrink: 0; position: relative; top: -1px; }
+.dw-specs-more[open] > summary::before { transform: rotate(45deg); top: -2px; }
+.dw-specs-more > summary::after { content: "+"; margin-left: auto; font-size: 18px; font-weight: 400; color: #999; line-height: 1; }
+.dw-specs-more[open] > summary::after { content: "\2013"; }
+.dw-specs-more > summary:hover { color: #000; }
+.dw-specs-more[open] > summary { border-bottom: none; }
+.dw-specs-more__body { padding-top: 2px; }
+/* Pattern description moved into the specs block when short enough (long ones stay wide) */
+.dw-specs-desc { margin-top: 14px; padding-top: 12px; border-top: 1px solid #eee; }
+.dw-specs-desc__label { font-size: 13px; font-weight: 600; color: #888; text-transform: uppercase; letter-spacing: 0.08em; margin-bottom: 6px; }
+.dw-specs-desc__body { font-size: 15px; color: #555; line-height: 1.5; }
+.dw-specs-desc__body p { margin: 0 0 8px; }
+.dw-specs-desc__body p:last-child { margin-bottom: 0; }
+</style>
+
+{% comment %} DW Specs — canonical namespace: custom.* — falls back to specs.* then global.* for legacy products {% endcomment %}
+
+{% assign has_specs = false %}
+{% assign w = product.metafields.custom.width.value | default: product.metafields.specs.width.value | default: product.metafields.global.Width.value | default: product.metafields.global.width.value %}
+{% comment %} bugfix 2026-07-16: specs.material (e.g. grasscloth lines) was missing from the chain, silently blanking Material; add specs.material + dwc.contents/content {% endcomment %}
+{% assign mat = product.metafields.custom.material.value | default: product.metafields.specs.material.value | default: product.metafields.specs.composition.value | default: product.metafields.dwc.contents.value | default: product.metafields.dwc.content.value | default: product.metafields.global.Content.value | default: product.metafields.global.Contents.value | default: product.metafields.global.Construction.value %}
+{% comment %} envelope-guard: unwrap a leaked raw metafield JSON wrapper e.g. {"type":"single_line_text_field","value":"Paper"} if one ever slips through, and prefer the canonical global.Material when the value is still an envelope {% endcomment %}
+{% if mat contains '"single_line_text_field"' and mat contains '"value"' %}{% assign mat = mat | split: '"value": "' | last | split: '"}' | first | default: product.metafields.global.Material.value %}{% endif %}
+{% assign col = product.metafields.custom.collection_name.value | default: product.metafields.specs.collection.value | default: product.metafields.global.Collection.value %}
+{% assign rep = product.metafields.custom.pattern_repeat.value | default: product.metafields.custom.repeat.value | default: product.metafields.specs.pattern_repeat.value | default: product.metafields.specs.repeat_v.value | default: product.metafields.dwc.repeat.value | default: product.metafields.global.repeat.value | default: product.metafields.global['Vert-Rpt'].value %}
+{% assign fin = product.metafields.custom.finish.value | default: product.metafields.specs.finish.value | default: product.metafields.global.Finish.value | default: product.metafields.global.FINISH.value %}
+{% assign care = product.metafields.custom.care.value | default: product.metafields.specs.care.value | default: product.metafields.global.Cleaning.value | default: product.metafields.global['Clean-Code'].value | default: product.metafields.global['Cleaning-Code'].value %}
+{% assign fire = product.metafields.custom.fire_rating.value | default: product.metafields.specs.fire_rating.value | default: product.metafields.global.fire_rating.value | default: product.metafields.global.FLAMMABILITY.value %}
+{% assign match = product.metafields.custom.match_type.value | default: product.metafields.specs.match_type.value | default: product.metafields.global.MATCH.value | default: product.metafields.global.Match.value %}
+{% assign app = product.metafields.custom.application.value | default: product.metafields.specs.application.value | default: product.metafields.global.application.value %}
+{% assign len = product.metafields.custom.length.value | default: product.metafields.global.length.value | default: product.metafields.global.Length.value %}
+{% assign pkg = product.metafields.custom.packaging.value | default: product.metafields.global.packaged.value | default: product.metafields.global.Packaged.value %}
+{% assign uom = product.metafields.custom.unit_of_measure.value | default: product.metafields.global.unit_of_measure.value %}
+{% assign coo = product.metafields.custom.origin.value | default: product.metafields.global.Country.value | default: product.metafields.global['Country-of-Origin'].value %}
+{% assign dur = product.metafields.custom.wyzenbeek.value | default: product.metafields.global['Wyzenbeek-#'].value %}
+{% assign mart = product.metafields.custom.martindale.value | default: product.metafields.global['Martindale-#'].value %}
+{% assign abr = product.metafields.custom.abrasion.value | default: product.metafields.specs.abrasion.value %}
+{% assign bk = product.metafields.custom.backing.value | default: product.metafields.specs.backing.value | default: product.metafields.global.Substrate.value | default: product.metafields.global.substrate.value %}
+{% assign brand = product.metafields.custom.designer.value | default: product.metafields.custom.brand.value | default: product.metafields.global.Brand.value %}
+{% assign wt = product.metafields.custom.product_weight.value | default: product.metafields.global.Weight.value %}
+{% comment %} spec-in-description cleanup 2026-07-28: rows for facts that had no home (were stuck in body_html tables/lists). Text-typed custom.* metafields written by specfix-all.mjs. {% endcomment %}
+{% assign certs = product.metafields.custom.certifications.value | default: product.metafields.specs.certifications.value %}
+{% assign gram = product.metafields.custom.grammage.value | default: product.metafields.specs.grammage.value %}
+{% assign lightfast = product.metafields.custom.light_fastness.value | default: product.metafields.specs.light_fastness.value %}
+{% comment %} residual spec rows 2026-07-29 (TK-10030 finish): peel-stick + compliance + roll-detail facts that had no home; fresh custom.* text keys (canonical was product_reference). {% endcomment %}
+{% assign wash = product.metafields.custom.washability.value %}
+{% assign removal = product.metafields.custom.removal.value %}
+{% assign adhesive = product.metafields.custom.adhesive.value %}
+{% assign coverage = product.metafields.custom.coverage.value | default: product.metafields.specs.coverage.value %}
+{% assign wallcov = product.metafields.custom.wall_coverage.value %}
+{% assign grade = product.metafields.custom.grade.value %}
+{% assign leadtime = product.metafields.custom.leadtime.value %}
+{% assign dims = product.metafields.custom.dimensions.value %}
+{% assign hrep = product.metafields.custom.horizontal_repeat.value %}
+{% assign prop65 = product.metafields.custom.prop65.value %}
+{% assign catb = product.metafields.custom.catb117.value %}
+{% assign minordtxt = product.metafields.custom.min_order.value %}
+{% comment %} blast-wide additions 2026-07-16 — TEXT-typed sources only (custom.style/type/horz_repeat/match/packaged are product_reference, never rendered) {% endcomment %}
+{% assign color = product.metafields.custom.color.value | default: product.metafields.specs.color.value | default: product.metafields.global.color.value | default: product.metafields.global.Color.value | default: product.metafields.dwc.color.value %}
+{% assign pname = product.metafields.custom.pattern_name.value | default: product.metafields.dwc.pattern_name.value %}
+{% assign style = product.metafields.specs.style.value | default: product.metafields.global.Style.value %}
+{% comment %} Type row dropped 2026-07-16: global.Type is redundant with product_type AND holds the banned word "Wallpapers" on ~14 products {% endcomment %}
+{% assign minord = product.metafields.custom.minimum.value | default: product.metafields.dwc.minimum_order_quantity.value | default: product.metafields.global.v_prods_quantity_order_min.value %}
+{% comment %} derive a human order unit from uom so "Minimum Order" isn't a bare integer; increment row dropped as redundant with min+packaged {% endcomment %}
+{% assign ulabel = '' %}
+{% if uom contains 'Double Roll' %}{% assign ulabel = 'double rolls' %}{% elsif uom contains 'Roll' %}{% assign ulabel = 'rolls' %}{% elsif uom contains 'Yard' %}{% assign ulabel = 'yards' %}{% elsif uom contains 'Meter' %}{% assign ulabel = 'meters' %}{% endif %}
+{% assign mfr = product.metafields.custom.manufacturer_sku.value | default: product.metafields.dwc.manufacturer_sku.value %}
+
+{% comment %} Double-roll lines (Thibaut/Malibu Wallpaper/Malibu Walls/York): explicit S/R + D/R
+   length metafields (D/R = 2 x S/R). AI Rooms note. Assigned up front so the collapsed-panel
+   presence check (has_more) can see them. (Steve 2026-07-08 "2 and 2") {% endcomment %}
+{% assign srlen = product.metafields.global.single_roll_length.value %}
+{% assign drlen = product.metafields.global.double_roll_length.value %}
+{% assign ai_note = product.metafields.specs.ai_rooms_note.value %}
+{% comment %} customer-facing DW SKU for the always-visible core: prefer the SELLABLE
+   (non-Sample) variant's SKU (the clean DW sku, e.g. KRAL-2023); only if every variant
+   is a Sample do we fall back to the first variant's SKU with the Sample suffix stripped
+   — handles both '-Sample' and a bare 'SAMPLE' suffix (Steve 2026-07-28). {% endcomment %}
+{% assign dwsku = '' %}
+{% for v in product.variants %}
+  {% unless v.title == 'Sample' or v.sku contains 'Sample' or v.sku contains 'SAMPLE' %}{% assign dwsku = v.sku %}{% break %}{% endunless %}
+{% endfor %}
+{% if dwsku == blank %}
+  {% assign dwsku = product.selected_or_first_available_variant.sku | default: product.variants.first.sku | remove: '-Sample' | remove: '-SAMPLE' | remove: 'Sample' | remove: 'SAMPLE' %}
+{% endif %}
+
+{% if product.type != blank or dwsku != blank or pname != blank or color != blank or w != blank or mat != blank or col != blank %}{% assign has_specs = true %}{% endif %}
+{% comment %} anything beyond the always-visible core → collapsed panel {% endcomment %}
+{% assign has_more = false %}
+{% if match != blank or mat != blank or bk != blank or wt != blank or col != blank or style != blank or brand != blank or fin != blank or fire != blank or care != blank or app != blank or pkg != blank or uom != blank or minord != blank or coo != blank or dur != blank or mart != blank or abr != blank or certs != blank or gram != blank or lightfast != blank or wash != blank or removal != blank or adhesive != blank or coverage != blank or wallcov != blank or grade != blank or leadtime != blank or dims != blank or hrep != blank or prop65 != blank or catb != blank or minordtxt != blank or ai_note != blank %}{% assign has_more = true %}{% endif %}
+
+{% if has_specs %}
+<div class="dw-specs">
+  <div class="dw-specs-title">Specifications</div>
+
+  {%- comment -%} ALWAYS-VISIBLE CORE (Steve 2026-07-28): Product Type, Pattern Name, Color, SKU {%- endcomment -%}
+  {% if product.type != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Product Type</span><span class="dw-spec-value">{{ product.type }}</span></div>{% endif %}
+  {% if pname != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Pattern Name</span><span class="dw-spec-value">{{ pname }}</span></div>{% endif %}
+  {% if color != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Color</span><span class="dw-spec-value">{{ color }}</span></div>{% endif %}
+  {% if dwsku != blank %}<div class="dw-spec-row"><span class="dw-spec-label">SKU</span><span class="dw-spec-value">{{ dwsku }}</span></div>{% endif %}
+  {%- comment -%} Width / Length / Pattern Repeat promoted to always-visible core (Steve TK-10095) {%- endcomment -%}
+  {% if w != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Width</span><span class="dw-spec-value">{{ w }}</span></div>{% endif %}
+  {% if srlen != blank and drlen != blank %}
+    <div class="dw-spec-row"><span class="dw-spec-label">Length S/R</span><span class="dw-spec-value">{{ srlen }}</span></div>
+    <div class="dw-spec-row"><span class="dw-spec-label">Length D/R</span><span class="dw-spec-value">{{ drlen }}</span></div>
+  {% elsif len != blank %}<div class="dw-spec-row"><span class="dw-spec-label">{% if product.vendor == 'Designtex' %}Bolt size{% else %}Length{% endif %}</span><span class="dw-spec-value">{{ len }}</span></div>{% endif %}
+  {% if rep != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Pattern Repeat</span><span class="dw-spec-value">{{ rep }}</span></div>{% endif %}
+
+  {%- comment -%} Everything else → collapsed "More specifications" panel (Steve 2026-07-28) {%- endcomment -%}
+  {% if has_more %}
+  <details class="dw-specs-more">
+    <summary>More specifications</summary>
+    <div class="dw-specs-more__body">
+      {% if match != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Match</span><span class="dw-spec-value">{{ match }}</span></div>{% endif %}
+      {% if mat != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Material</span><span class="dw-spec-value">{{ mat }}</span></div>{% endif %}
+      {% if bk != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Backing</span><span class="dw-spec-value">{{ bk }}</span></div>{% endif %}
+      {% if wt != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Weight</span><span class="dw-spec-value">{{ wt }}</span></div>{% endif %}
+      {% if gram != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Grammage</span><span class="dw-spec-value">{{ gram }}</span></div>{% endif %}
+      {% if lightfast != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Light Fastness</span><span class="dw-spec-value">{{ lightfast }}</span></div>{% endif %}
+      {% if col != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Collection</span><span class="dw-spec-value">{{ col }}</span></div>{% endif %}
+      {% if style != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Style</span><span class="dw-spec-value">{{ style }}</span></div>{% endif %}
+      {% if brand != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Designer</span><span class="dw-spec-value">{{ brand }}</span></div>{% endif %}
+      {% if fin != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Finish</span><span class="dw-spec-value">{{ fin }}</span></div>{% endif %}
+      {% if fire != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Fire Rating</span><span class="dw-spec-value">{{ fire }}</span></div>{% endif %}
+      {% if certs != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Certifications</span><span class="dw-spec-value">{{ certs }}</span></div>{% endif %}
+      {% if dims != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Dimensions</span><span class="dw-spec-value">{{ dims }}</span></div>{% endif %}
+      {% if coverage != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Coverage</span><span class="dw-spec-value">{{ coverage }}</span></div>{% endif %}
+      {% if wallcov != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Wall Coverage</span><span class="dw-spec-value">{{ wallcov }}</span></div>{% endif %}
+      {% if hrep != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Horizontal Repeat</span><span class="dw-spec-value">{{ hrep }}</span></div>{% endif %}
+      {% if grade != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Grade</span><span class="dw-spec-value">{{ grade }}</span></div>{% endif %}
+      {% if wash != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Washability</span><span class="dw-spec-value">{{ wash }}</span></div>{% endif %}
+      {% if removal != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Removal</span><span class="dw-spec-value">{{ removal }}</span></div>{% endif %}
+      {% if adhesive != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Adhesive</span><span class="dw-spec-value">{{ adhesive }}</span></div>{% endif %}
+      {% if leadtime != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Lead Time</span><span class="dw-spec-value">{{ leadtime }}</span></div>{% endif %}
+      {% if minordtxt != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Minimum Order</span><span class="dw-spec-value">{{ minordtxt }}</span></div>{% endif %}
+      {% if prop65 != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Prop 65</span><span class="dw-spec-value">{{ prop65 }}</span></div>{% endif %}
+      {% if catb != blank %}<div class="dw-spec-row"><span class="dw-spec-label">CA TB117</span><span class="dw-spec-value">{{ catb }}</span></div>{% endif %}
+      {% if care != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Care</span><span class="dw-spec-value">{{ care }}</span></div>{% endif %}
+      {% if app != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Application</span><span class="dw-spec-value">{{ app }}</span></div>{% endif %}
+      {% if pkg != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Packaged</span><span class="dw-spec-value">{{ pkg }}</span></div>{% endif %}
+      {% if uom != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Unit</span><span class="dw-spec-value">{{ uom }}</span></div>{% endif %}
+      {% if minord != blank and minord != '1' and minord != 1 %}<div class="dw-spec-row"><span class="dw-spec-label">Minimum Order</span><span class="dw-spec-value">{{ minord }}{% if ulabel != blank %} {{ ulabel }}{% endif %}</span></div>{% endif %}
+      {% if coo != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Origin</span><span class="dw-spec-value">{{ coo }}</span></div>{% endif %}
+      {% comment %} mfr SKU row removed 2026-07-16 (DTD verdict A): raw mfr code is a private-label reverse-lookup key — backend metafield only, never customer-facing {% endcomment %}
+      {% if dur != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Wyzenbeek</span><span class="dw-spec-value">{{ dur }}</span></div>{% endif %}
+      {% if mart != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Martindale</span><span class="dw-spec-value">{{ mart }}</span></div>{% endif %}
+      {% if abr != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Abrasion</span><span class="dw-spec-value">{{ abr }}</span></div>{% endif %}
+      {% if ai_note != blank %}
+      <div class="dw-spec-row" style="margin-top: 10px; padding-top: 10px; border-top: 1px solid #eee;">
+        <span class="dw-spec-label" style="color: #b45309; font-size: 13px;">AI Rooms</span>
+        <span class="dw-spec-value" style="color: #92400e; font-size: 13px; font-style: italic;">{{ ai_note }}</span>
+      </div>
+      {% endif %}
+    
+  {%- comment -%} TK-10311/TK-00034: site-wide quote-only "specs confirmed at sample" trust note (DTD 6/6 A, Steve-approved 2026-08-08) {%- endcomment -%}
+  {%- if product.metafields.custom.price_mode == 'quote_only' or product.tags contains 'quotes' -%}
+    <p class="spec-note spec-note--quote">Exact width &amp; specifications are confirmed with your complimentary sample.</p>
+  {%- endif -%}
+    </div>
+  </details>
+  {% endif %}
+
+  {%- comment -%} PATTERN DESCRIPTION moved into the specs block when short enough (Steve 2026-07-28).
+     Long descriptions (e.g. Kravet-family products) exceed DESC_MAX chars and stay in the wide
+     gallery area instead — product-gallery.liquid gates on the SAME threshold so it renders in
+     exactly one place. DESC_MAX is tunable. {%- endcomment -%}
+  {% assign desc_len = product.description | strip_html | strip | size %}
+  {% if product.description != blank and desc_len <= 1500 %}
+    <div class="dw-specs-desc">
+      <div class="dw-specs-desc__label">Description</div>
+      <div class="dw-specs-desc__body rte" itemprop="description">{{ product.description }}</div>
+    </div>
+  {% endif %}
+</div>
+{% endif %}
+
+{% comment %}
+Contact popup form
+{% endcomment %}
+<div id="custom-popup-overlay">
+  <div class="custom-popup-main">
+    <div class="custom-close">&times;</div>
+    <div class="content-main">
+      <div class="popup-content">
+        <h2 style="text-align:center;">Send us a message</h2>
+        {% form 'contact' %}
+        {% if form.errors %}<div class="error-message" style="display:none;"><span>{{ 'general.contact.error' | t }}</span></div>{% endif %}
+        {% if form.posted_successfully? %}<div class="success-message" style="display:none;">{{ 'general.contact.success' | t }}</div>{% endif %}
+        <div class="field-wrap sku"><input type="text" value="{{ product.variants.last.sku }}" name="contact[SKU]" readonly="true"></div>
+        <div class="field-wrap name"><input type="text" placeholder="Name" value="" name="contact2[name]" class="{% if form.errors contains 'author' %}error{% endif %}" required></div>
+        <div class="field-wrap email"><input type="email" name="contact[email]" autocorrect="off" autocapitalize="off" value="{% if form.email %}{{ form.email }}{% elsif customer %}{{ customer.email }}{% endif %}" class="{% if form.errors contains 'email' %}input--error{% endif %}" placeholder="Email" required></div>
+        <div class="field-wrap phone"><input type="text" placeholder="Phone number" value="" name="contact[phone number]" required></div>
+        <div class="field-wrap message"><textarea name="contact[message]" placeholder="Message" rows="5" required></textarea></div>
+        <input type="hidden" class="comment-check" value="" />
+        <input style="background-color:#000;color:#fff;" type="submit" value="{{ 'general.contact.submit' | t }}">
+        {% endform %}
+      </div>
+    </div>
+  </div>
+</div>
\ No newline at end of file

← 73033e4 auto-data-snapshot: 2026-09-02T13:25:17 (1 data files) — pri  ·  back to Flock Fix Viewer  ·  board: sqft + specs chips, pinned port 61438; sqft/enrich/th 2e5e767 →